# Lynkow Docs > Lynkow is an all-in-one headless CMS platform providing content management, analytics, forms, reviews, and SEO tools in a single integrated solution. ## Search This site has a full-text search API with typo tolerance. Instead of reading all the content below, you can search for specific topics. **Endpoint:** ``` GET https://search.lynkow.com/public/c3bc8a0a-7820-4945-b174-5c9328b98719/search?q={query} ``` **Parameters:** `q` (required), `locale`, `category` (slug), `tag` (slug), `page`, `limit` (max 100) **Example:** ``` GET https://search.lynkow.com/public/c3bc8a0a-7820-4945-b174-5c9328b98719/search?q=getting+started&limit=5 ``` Returns `{ data: [{ id, title, slug, path, excerpt, categories, tags, publishedAt }], meta: { total, page, totalPages, query, processingTimeMs } }` --- # Cloudflare Cache Purge **Publié le** : 2026-04-10 **Catégorie** : Plugins > Automatically invalidate your Cloudflare CDN cache whenever your Lynkow content changes, so visitors always see the latest version of your site. ## Why This Plugin? If you put your site behind Cloudflare, visitors are served from Cloudflare's edge cache. Without this plugin, that cache can stay stale for minutes or hours after you publish new content. The Cloudflare Cache Purge plugin solves this by automatically clearing your Cloudflare cache whenever you update something in Lynkow. No server to host, no code to write, no manual purging. ## Prerequisites Before installing the plugin, you need: - A **Cloudflare account** with at least one zone (a domain pointing to Cloudflare nameservers) - **Admin access** to your Lynkow site - **2 minutes** to create an API token You do **not** need a paid Cloudflare plan. The Free tier is supported. ## Step 1 — Create a Cloudflare API Token The plugin needs a Cloudflare API token with permission to purge cache. We recommend creating a dedicated token scoped to **only** the purge permission. 1. Open [https://dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens) while logged into Cloudflare 2. Click **"Create Token"** 3. Scroll to the bottom and click **"Get started"** on the **"Create Custom Token"** row 4. Configure the token: - **Token name** : `Lynkow Cache Purge` - **Permissions** : add one row with these three columns - Column 1: `Zone` - Column 2: `Cache Purge` - Column 3: `Purge` - **Zone Resources** : set to - `Include` → `All zones from an account` → select your Cloudflare account - **Client IP Address Filtering** : leave empty (optional) - **TTL** : leave as default (no expiration) 5. Click **"Continue to summary"**, verify the permissions, then click **"Create Token"** 6. **Copy the token immediately** — Cloudflare only shows it once. If you lose it, you will need to delete it and create a new one. The resulting token has **only** the `Cache Purge` permission and access to all zones in your Cloudflare account. It cannot read or modify anything else. ## Step 2 — Install the Plugin in Lynkow 1. In your Lynkow admin dashboard, open the **Plugins** page from the left sidebar 2. In the **"Available"** section, find the **Cloudflare Cache Purge** plugin and click **Install** 3. A three-step drawer opens on the right side of the screen. ### Step 2.1 — Choose Events The first step lets you select which types of changes should trigger a cache purge. Sensible defaults are pre-selected. Common event groups: - **content** — articles created, updated, published, deleted - **site_block** — page layouts, global blocks - **category** — category changes - **tag** — tag changes - **review** — when a review is moderated and appears on a page - **redirect** — when URL redirects change - **variable** — when template variables are updated Select only the events that actually affect cached pages on your site. If you are not sure, leave the defaults. Click **Next**. ### Step 2.2 — Paste the API Token Paste the Cloudflare API token you created in Step 1 into the **API Token** field. Click **Next**. The plugin verifies the token with Cloudflare and loads the list of zones it has access to. If the token is invalid, you will see an error and remain on this step. ### Step 2.3 — Pick Zone and Strategy - **Zone** : a dropdown with all the Cloudflare zones the token has access to. Select the zone that serves your site. - **Stratégie de purge** (Purge strategy) : - **Par préfixe (recommandé)** : Smart purge. Only the URLs affected by each change are cleared from the cache. Fast and efficient. - **Purge complète** : The entire cache for the zone is cleared on every change. Use this if you prefer a simpler mental model or if targeted purges miss some pages on your site. - **Domaine personnalisé** (Custom domain) : Optional. Click **Ajouter** if your Cloudflare zone uses a different domain than the one configured in Lynkow (for example, `www.example.com` vs `example.com`). Leave it empty otherwise. Click **Install** to save. The plugin now appears in the **"Installed"** section with a green status dot. ## Step 3 — Verify It Works Test the plugin without touching your actual content: 1. On the installed plugin row, hover to reveal the action buttons 2. Click the **test tube** icon (Test) 3. A toast message appears: - **Success** : the plugin was able to call Cloudflare. You are done. - **Error** : the message tells you what went wrong. The most common cause is an expired or revoked API token. You can also click the **file icon** to see the execution history with: - Event name - Success/error status - Execution duration - Timestamp This is the best place to debug unexpected behavior. ## Strategy Comparison | Strategy | Cache impact | Recommended for | | --- | --- | --- | | **Par préfixe** | Only affected pages are cleared | Most sites — fast and efficient | | **Purge complète** | Entire zone cache is cleared | Complex sites where targeted purges miss pages | Start with **Par préfixe**. If you notice stale content after updates, switch to **Purge complète**. ## Troubleshooting ### "Zone ID ou API Token invalide" Cloudflare returned an error when the plugin tried to verify your credentials. Check that: - The token has not expired or been revoked - The token has the `Zone.Cache Purge` permission - The token has access to **at least one zone** Create a new token if in doubt and update the plugin settings. ### Plugin logs show `success` but my cache is still stale Cloudflare cache purge takes a few seconds to propagate across edge servers. If content is still stale after 30 seconds: 1. Check the plugin logs to see what was purged 2. If the page you are testing is not visibly there, switch to the **"Purge complète"** strategy ### Rate-limit errors Cloudflare rate-limits the purge API. If you are saving many pieces of content in quick succession, you may hit this limit. Temporarily disable the plugin during bulk operations (toggle it off, do your work, toggle it back on). ## Security Your Cloudflare API token is encrypted at rest and masked in the admin UI after saving (shown as `••••a3f2`). It is never exposed in API responses or logs. ## Uninstall To remove the plugin: 1. Open the Plugins page 2. Hover over the Cloudflare Cache Purge row in the "Installed" section 3. Click the trash icon 4. Confirm in the dialog Uninstalling deletes all plugin settings and execution history. Your Cloudflare token remains valid — delete it from the Cloudflare dashboard if you no longer need it. --- # Visual Editor **Publié le** : 2026-04-10 **Catégorie** : Advanced Integrate the Lynkow Visual Editor to let CMS admins edit page content directly on your website. The admin dashboard opens your site in an iframe overlay, and the SDK on your site communicates via PostMessage to enable inline editing with real-time preview. --- ## Prerequisites Before starting, ensure you have: - A **Lynkow site** with at least one Site Block (page section) configured in the admin dashboard - A **Next.js 15** frontend with the App Router - The **`lynkow`**** SDK** installed: ```bash npm install lynkow ``` - The site's **Preview URL** configured in the Lynkow admin under **Settings > Site > Preview URL** (covered in [Step 6](#step-6-configure-preview-url-in-admin)) - The **`NEXT_PUBLIC_CMS_ORIGIN`** environment variable set in your `.env` file (see [Step 0](#step-0-environment-variable)) --- ## Step 0: Environment Variable The Visual Editor requires knowing the origin of the Lynkow admin dashboard. This is used for CSP headers (to allow iframe embedding) and PostMessage origin validation (to accept only messages from the dashboard). Add this variable to your `.env.local` (development) and your production environment: ```bash # REQUIRED — the Visual Editor will not work without this NEXT_PUBLIC_CMS_ORIGIN=https://manage.lynkow.com ``` > **Important:** `NEXT_PUBLIC_*` variables in Next.js are injected at **build time**, not runtime. After adding or changing this variable, you must **rebuild and redeploy** your site. --- ## Step 1: Configure CSP Headers Browsers block pages from being displayed inside iframes by default. For the Visual Editor to work, your site must explicitly allow the Lynkow admin dashboard to embed it. This requires two header changes: 1. **Set ****`Content-Security-Policy: frame-ancestors`** to allow the CMS origin 2. **Remove ****`X-Frame-Options`** which would override CSP and block embedding ### Option A: SDK Middleware Helper (Recommended) The SDK provides a Next.js middleware helper that handles both headers automatically. There are two ways to use it depending on whether you already have a custom middleware. **Standalone (no existing middleware):** ```typescript // middleware.ts import { lynkowPreviewMiddleware } from 'lynkow/middleware/next' export default lynkowPreviewMiddleware({ cmsOrigin: process.env.NEXT_PUBLIC_CMS_ORIGIN || 'https://manage.lynkow.com', }) export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', ], } ``` **Composition (wrapping an existing middleware):** ```typescript // middleware.ts import { withLynkowPreview } from 'lynkow/middleware/next' import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' function baseMiddleware(request: NextRequest) { // Your existing middleware logic here return NextResponse.next() } export default withLynkowPreview(baseMiddleware, { cmsOrigin: process.env.NEXT_PUBLIC_CMS_ORIGIN || 'https://manage.lynkow.com', }) export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', ], } ``` ### Option B: Manual Headers in next.config.ts If you prefer not to use the SDK middleware helper, configure the headers directly in your Next.js config. ```typescript // next.config.ts import type { NextConfig } from 'next' const CMS_ORIGIN = process.env.NEXT_PUBLIC_CMS_ORIGIN || 'https://manage.lynkow.com' const nextConfig: NextConfig = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'Content-Security-Policy', value: `frame-ancestors 'self' ${CMS_ORIGIN}`, }, // X-Frame-Options is intentionally omitted so it does not // override the frame-ancestors directive above. ], }, ] }, } export default nextConfig ``` > **Important:** If your existing middleware or config already sets `X-Frame-Options: DENY` or `SAMEORIGIN`, you must remove it. That header takes precedence over `frame-ancestors` in some browsers and will block the iframe entirely. --- ## Step 2: Add the React Provider Wrap your application in the `` provider. This component handles the PostMessage handshake with the admin dashboard, scans the page for editable fields, and activates the overlay UI. It only activates when the page is loaded with the `?lynkow-preview=true` query parameter (which the admin dashboard appends automatically). In production, it adds zero overhead -- no event listeners, no DOM scanning, no network requests. ```typescript // app/layout.tsx import { LynkowVisualEditor } from 'lynkow/visual-editor/react' import type { ReactNode } from 'react' export default function RootLayout({ children }: { children: ReactNode }) { return ( {children} ) } ``` The `cmsOrigin` prop is required and must match the exact URL of your Lynkow admin dashboard (`https://manage.lynkow.com`). This is used to verify PostMessage origins for security -- messages from other origins are ignored. --- ## Step 3: Mark Editable Fields with Data Attributes The Visual Editor discovers editable content on your page through HTML data attributes. When the page loads inside the iframe, the SDK scans the DOM for these attributes and sends the field map to the admin dashboard. There are four attributes: | Attribute | Purpose | Example | | --- | --- | --- | | `data-lynkow-block` | Marks a container as an editable block. Must match a Site Block slug in the CMS. | `data-lynkow-block="header"` | | `data-lynkow-field` | Marks an element as an editable field within a block. | `data-lynkow-field="title"` | | `data-lynkow-type` | Field type. Determines what editor UI the admin sees. | `data-lynkow-type="text"` | | `data-lynkow-label` | Human-readable label shown in the editor overlay. | `data-lynkow-label="Page Title"` | Supported field types: `text`, `image`, `richtext`, `array`, `object`, `boolean`, `number`, `select`, `color`, `url`, `email`, `date`. Here is a complete page component with annotated editable fields: ```typescript // app/page.tsx import { lynkowClient } from '@/lib/lynkow' export default async function HomePage() { // Fetch block data from the Lynkow SDK at build/request time const header = await lynkowClient.getBlock('header') return (
{/* Block container -- slug must match a Site Block in the CMS */}
{/* Image field */} Logo {/* Text field */}

{header.title}

{/* Rich text field */}
{/* Array field (e.g., navigation links) */}
) } ``` > **Nesting rule:** Every `data-lynkow-field` element must be a descendant of a `data-lynkow-block` element. Fields outside a block are ignored by the scanner. --- ## Step 4: Use Hooks for Live Preview Data When an admin edits a field in the Visual Editor, the changes are sent to your site via PostMessage. The `useBlockData` hook subscribes to these messages and returns live data that updates in real-time, giving the admin an instant preview of their changes. In production (outside the iframe), `useBlockData` simply returns the `initialData` you pass to it -- no subscriptions, no overhead. ### useBlockData Returns the full block data object, updated in real-time during editing. ```typescript 'use client' import { useBlockData } from 'lynkow/visual-editor/react' interface HeaderData { title: string description: string logo: string links: { label: string; url: string }[] } export function Header({ initialData }: { initialData: HeaderData }) { // In preview mode: returns live data that updates as the admin edits // In production: returns initialData unchanged const data = useBlockData('header', initialData) return (
Logo

{data.title}

) } ``` ### useLynkowField Returns the value of a single field. Useful when a field value is needed in isolation, for example to conditionally render a section. ```typescript 'use client' import { useLynkowField } from 'lynkow/visual-editor/react' export function AnnouncementBanner() { const message = useLynkowField('banner', 'message') const isVisible = useLynkowField('banner', 'visible') if (!isVisible) return null return (

{message as string}

) } ``` ### useIsPreviewMode Returns `true` when the site is loaded inside the Visual Editor iframe. Use this to show preview-only UI or hide elements that should not appear during editing. ```typescript 'use client' import { useIsPreviewMode } from 'lynkow/visual-editor/react' export function Footer() { const isPreview = useIsPreviewMode() return ( ) } ``` --- ## Step 5: Complete Example A full working implementation with all the pieces wired together. ### File: middleware.ts ```typescript // middleware.ts import { lynkowPreviewMiddleware } from 'lynkow/middleware/next' export default lynkowPreviewMiddleware({ cmsOrigin: process.env.NEXT_PUBLIC_CMS_ORIGIN || 'https://manage.lynkow.com', }) export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)', ], } ``` ### File: lib/lynkow.ts ```typescript // lib/lynkow.ts import { createClient } from 'lynkow' export const lynkowClient = createClient({ siteId: process.env.LYNKOW_SITE_ID!, apiUrl: process.env.LYNKOW_API_URL || 'https://api.lynkow.com', apiToken: process.env.LYNKOW_API_TOKEN!, }) ``` ### File: app/layout.tsx ```typescript // app/layout.tsx import { LynkowVisualEditor } from 'lynkow/visual-editor/react' import type { ReactNode } from 'react' import './globals.css' export default function RootLayout({ children }: { children: ReactNode }) { return ( {children} ) } ``` ### File: app/page.tsx ```typescript // app/page.tsx import { lynkowClient } from '@/lib/lynkow' import { Header } from '@/components/header' import { FeatureGrid } from '@/components/feature-grid' export default async function HomePage() { // Server-side: fetch block data from the Lynkow API const [headerData, featuresData] = await Promise.all([ lynkowClient.getBlock('header'), lynkowClient.getBlock('features'), ]) return (
{/* Pass server-fetched data as initialData to client components */}
) } ``` ### File: components/header.tsx ```typescript // components/header.tsx 'use client' import { useBlockData } from 'lynkow/visual-editor/react' interface HeaderData { title: string description: string logo: string ctaText: string ctaUrl: string links: { label: string; url: string }[] } export function Header({ initialData }: { initialData: HeaderData }) { const data = useBlockData('header', initialData) return (
Logo

{data.title}

{data.ctaText}
) } ``` ### File: components/feature-grid.tsx ```typescript // components/feature-grid.tsx 'use client' import { useBlockData } from 'lynkow/visual-editor/react' interface Feature { icon: string title: string description: string } interface FeaturesData { heading: string items: Feature[] } export function FeatureGrid({ initialData }: { initialData: FeaturesData }) { const data = useBlockData('features', initialData) return (

{data.heading}

{data.items?.map((feature, i) => (
{feature.icon}

{feature.title}

{feature.description}

))}
) } ``` ### File: .env.local ```bash # .env.local LYNKOW_SITE_ID=your-site-uuid LYNKOW_API_URL=https://api.lynkow.com LYNKOW_API_TOKEN=your-api-token # REQUIRED — Visual Editor will not work without this NEXT_PUBLIC_CMS_ORIGIN=https://manage.lynkow.com ``` --- ## Step 6: Configure Preview URL in Admin The admin dashboard needs to know where your frontend is hosted so it can load it in the Visual Editor iframe. 1. Open the Lynkow admin dashboard 2. Go to **Settings > Site** 3. Find the **Preview URL** field 4. Enter your frontend URL (e.g., `https://www.example.com`) 5. Save When an admin clicks "Visual Editor" in the admin dashboard, Lynkow appends `?lynkow-preview=true` to the Preview URL and loads it in an iframe. The SDK on your site detects this parameter and initiates the PostMessage handshake. > **Development tip:** The admin dashboard runs on HTTPS (`https://manage.lynkow.com`), so the Preview URL **must also use HTTPS**. Browsers block HTTP iframes inside HTTPS pages (mixed content). For local development, use a tunnel to expose your dev server over HTTPS: > > ```bash > # Using cloudflared (free, no account required) > cloudflared tunnel --url http://localhost:3000 > > # Or using ngrok > ngrok http 3000 > ``` > > Then set the HTTPS tunnel URL (e.g., `https://xyz.trycloudflare.com`) as your Preview URL. Update it to your production URL before going live. --- ## Troubleshooting When the Visual Editor fails to connect, the admin dashboard displays an **"SDK Visual Editor not detected"** error. This means it did not receive a `lynkow:handshake` PostMessage from the iframe within 5 seconds. Here are the possible causes and how to diagnose each one. ### Mixed Content (HTTPS → HTTP) **Symptom:** The iframe is blank. No error in the dashboard console, no CSP violation — the browser silently refuses to load the iframe. **Cause:** The admin dashboard runs on `https://manage.lynkow.com`. If your Preview URL uses `http://` (e.g., `http://localhost:3000`), the browser blocks it as mixed content. This is the most common issue during local development. **Fix:** Use an HTTPS tunnel for local development: ```bash # cloudflared (free, no account) cloudflared tunnel --url http://localhost:3000 # or ngrok ngrok http 3000 ``` Set the resulting HTTPS URL as your Preview URL in **Settings > Site > Preview URL**. ### CSP Blocking the Iframe **Symptom:** The iframe is blank or shows an error page. The browser console on the admin dashboard shows: ``` Refused to display 'https://www.example.com/' in a frame because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'" ``` **Fix:** Ensure your CSP headers include the Lynkow admin origin: ``` Content-Security-Policy: frame-ancestors 'self' https://manage.lynkow.com ``` Also verify that no `X-Frame-Options` header is being set (by your hosting provider, CDN, or reverse proxy), as it overrides `frame-ancestors` in some browsers. Check all layers: ```bash curl -I https://www.example.com | grep -i "frame\|content-security" ``` ### Missing SDK / Provider Not Mounted **Symptom:** The iframe loads your site correctly, but the editor overlay never appears. **Fix:** Verify that `` is in the component tree. It must wrap the content of `` in your root layout: ```typescript // app/layout.tsx -- confirm this is present {children} ``` Check the browser console inside the iframe (right-click the iframe > "Inspect frame" in Chrome DevTools) for import errors like `Cannot find module 'lynkow/visual-editor/react'`, which indicates the SDK is not installed. ### Wrong cmsOrigin **Symptom:** The iframe loads and the SDK initializes, but the handshake never completes. No errors in the console. **Fix:** The `cmsOrigin` prop must match the admin dashboard URL exactly, including the protocol and without a trailing slash: ```typescript // Correct — always use the environment variable // Wrong -- hardcoded URL (will break if the dashboard URL changes) // Wrong -- trailing slash // Wrong -- wrong protocol ``` The SDK validates the PostMessage origin against this value. If they do not match, messages are silently ignored as a security measure. ### No Data Attributes on the Page **Symptom:** The editor overlay appears but shows no editable fields. **Fix:** At least one element with `data-lynkow-block` must exist on the page. Every `data-lynkow-field` must be nested inside a `data-lynkow-block` container. Verify with DevTools: ```javascript // Run in the iframe's console document.querySelectorAll('[data-lynkow-block]').length // Should be >= 1 document.querySelectorAll('[data-lynkow-field]').length // Should be >= 1 ``` Also confirm that the block slugs in your HTML match the Site Block slugs configured in the CMS admin. Mismatched slugs are silently ignored. ### Preview URL Not Set **Symptom:** Clicking "Visual Editor" in the admin dashboard does nothing or shows a configuration prompt. **Fix:** Set the Preview URL in **Settings > Site > Preview URL**. This tells the admin dashboard which URL to load in the iframe. ### JavaScript Errors During Initialization **Symptom:** The iframe loads but the SDK fails silently due to a runtime error. **Fix:** Open DevTools on the iframe (right-click > "Inspect frame") and check the console for errors. Common issues: - **Hydration mismatch:** If you use `useBlockData` in a component, ensure the component has the `'use client'` directive. Server Components cannot use hooks. - **Missing ****`NEXT_PUBLIC_CMS_ORIGIN`****:** This is the most common cause. If the variable is undefined, the provider receives an empty string and the handshake silently fails. Verify your `.env.local` contains `NEXT_PUBLIC_CMS_ORIGIN=https://manage.lynkow.com` and that you **rebuilt** your app after adding it (Next.js injects `NEXT_PUBLIC_*` at build time, not runtime). - **Conflicting middleware:** If another middleware returns a response before the Lynkow middleware runs, the CSP headers will not be set. Ensure the Lynkow middleware runs on all routes. ### Quick Diagnostic Checklist | Check | How to Verify | | --- | --- | | `NEXT_PUBLIC_CMS_ORIGIN` set | `.env.local` contains `NEXT_PUBLIC_CMS_ORIGIN=https://manage.lynkow.com` | | App rebuilt after env change | `NEXT_PUBLIC_*` vars are injected at build time — redeploy after changing | | SDK installed | `npm ls lynkow` shows the package | | Provider mounted | Search codebase for `LynkowVisualEditor` in a layout file | | CSP headers set | `curl -I your-site.com` includes `frame-ancestors` with the CMS origin | | No X-Frame-Options | `curl -I your-site.com` does NOT include `X-Frame-Options` | | cmsOrigin correct | Matches admin URL exactly (no trailing slash) | | Data attributes present | At least one `data-lynkow-block` element on the page | | Block slugs match | HTML slugs match Site Block slugs in CMS admin | | Preview URL configured | Set in admin Settings > Site > Preview URL | | Preview URL uses HTTPS | No `http://` — browsers block mixed content in iframes | | Query param present | URL ends with `?lynkow-preview=true` when loaded in iframe | --- # Complete Integration Checklist **Publié le** : 2026-04-10 **Catégorie** : Getting Started Use this checklist to verify your Lynkow integration covers every feature. Each item links to the detailed guide. Skip items marked (optional) if not needed for your project. > **FOR LLMs:** This is the master checklist for integrating Lynkow. Follow each step in order. Full documentation at [lynkow.com/docs](https://lynkow.com/docs). Machine-readable: [/llms.txt](https://lynkow.com/docs/llms.txt) and [/llms-full.txt](https://lynkow.com/docs/llms-full.txt). --- ## SDK Integration ### 1. SDK Setup - Install the SDK: `npm install lynkow` - Create shared client in `lib/lynkow.ts` with `createClient({ siteId })` - Set `NEXT_PUBLIC_LYNKOW_SITE_ID` in `.env.local` - Configure ISR cache: `fetchOptions: { next: { revalidate: 60 } }` → Guide: **Quick Start** ### 2. Pages & Site Configuration - Fetch site config with `lynkow.globals.siteConfig()` for header/footer - Render pages with `lynkow.pages.getBySlug()` or `getByPath()` - Build navigation from `lynkow.pages.list()` - Handle 404 with `isLynkowError()` + `notFound()` → Guide: **Pages & Site Configuration** ### 3. Blog / Content - List articles with `lynkow.contents.list()` + pagination - Single article page with `lynkow.contents.getBySlug()` - Render HTML body with `dangerouslySetInnerHTML` - Display author, categories, tags - Featured images with `featuredImageVariants` (responsive) → Guide: **Build a Blog with Next.js** ### 4. Categories & Tags - Category pages with `lynkow.categories.getBySlug()` - Category tree with `lynkow.categories.tree()` - Tag filtering with `lynkow.contents.list({ tag: 'slug' })` → Guide: **Build a Blog with Next.js** ### 5. Catch-all Routes & Path Resolution - Implement `app/[...slug]/page.tsx` with `lynkow.paths.resolve()` - Use `isContentResolve()` / `isCategoryResolve()` type guards - Static generation with `lynkow.paths.list()` in `generateStaticParams()` - Handle redirections with `lynkow.paths.matchRedirect()` in middleware → Guide: **Catch-all Routes & Path Resolution** ### 6. Dynamic Forms - Fetch form schema with `lynkow.forms.getBySlug()` - Render fields dynamically from `form.schema` - Client-side validation from `field.validation` - Submit with `lynkow.forms.submit()` (honeypot is automatic) - Handle success vs pending (double opt-in) - (optional) reCAPTCHA v3 integration if `form.recaptchaEnabled` - Configure Preview URL for localhost development (avoids 403) → Guide: **Dynamic Forms** ### 7. Customer Reviews - List reviews with `lynkow.reviews.list()` - Display star ratings and author info - Submit reviews with `lynkow.reviews.submit()` - Handle moderation (pending vs approved) - Check settings with `lynkow.reviews.settings()` → Guide: **Customer Reviews** ### 8. Media & Image Optimization - Use `featuredImageVariants` presets (thumbnail, card, hero, og) - Build responsive images with `lynkow.media.srcset()` - Single transforms with `lynkow.media.transform()` - (optional) Custom Next.js Image loader - (optional) Blur placeholder with tiny transform → Guide: **Media & Image Optimization** ### 9. Multi-language (optional) - Set default locale in client config - Per-request locale override: `{ locale: 'fr' }` - Build locale switcher from `content.structuredData.alternates` - Generate hreflang tags - Static generation per locale with `paths.list({ locale })` - (optional) Next.js middleware for locale detection → Guide: **Multi-language (i18n)** ### 10. Structured Content (optional) - Detect structured content: `content.customData !== null` - Fetch category schema for field type info - Render richtext fields with `dangerouslySetInnerHTML` - Handle image, select, array, object field types → Guide: **Structured Content** ### 11. SEO — Meta Tags & Structured Data - `generateMetadata()` with `metaTitle`, `metaDescription`, `keywords` - Open Graph: `ogImage`, `ogImageVariants` - Canonical URLs: `canonicalUrl` with fallback - JSON-LD: inject `content.structuredData.article.jsonLd` - FAQ JSON-LD: inject `content.structuredData.faq.jsonLd` if present → Guide: **SEO & Analytics** (sections 1-4) ### 12. SEO — Sitemap, Robots, LLMs - XML Sitemap: `app/sitemap.xml/route.ts` with `lynkow.seo.sitemap()` - Robots.txt: `app/robots.txt/route.ts` with `lynkow.seo.robots()` - llms.txt: `app/llms.txt/route.ts` with `lynkow.seo.llmsTxt()` - llms-full.txt: `app/llms-full.txt/route.ts` with `lynkow.seo.llmsFullTxt()` - Per-article Markdown: `.md` route with `lynkow.seo.getMarkdown()` - Add `` in layout `` → Guides: **SEO & Analytics** (sections 5-7) + **LLM-Ready Content** ### 13. Analytics - Initialize tracker: `lynkow.analytics.init()` - Track SPA navigation: `lynkow.analytics.trackPageview({ path })` - (optional) Custom events: `lynkow.analytics.trackEvent()` - **GDPR: If EU site, do NOT init without consent — see step 14** → Guide: **SEO & Analytics** (sections 8-9) ### 14. GDPR Cookie Consent - Show consent banner: `lynkow.consent.show()` - Conditional analytics: disable until consent granted - Listen for changes: `lynkow.on('consent-changed', cb)` - Enable/disable tracking based on `categories.analytics` - (optional) Custom consent UI with `acceptAll()`, `rejectAll()`, `setCategories()` → Guide: **SEO & Analytics** (section 10) ### 15. Webhooks & Cache Revalidation - Create webhook in admin: Settings > Webhooks - Implement `app/api/revalidate/route.ts` handler - Verify HMAC-SHA256 signature - Map events to `revalidatePath()` / `revalidateTag()` - Handle: content.published, content.updated, content.deleted, site_block.published → Guide: **Webhooks & Cache Revalidation** ### 16. Error Handling - Use `isLynkowError()` type guard in catch blocks - NOT_FOUND → `notFound()` in Next.js - RATE_LIMITED → exponential backoff retry - VALIDATION_ERROR → display field errors from `details[]` - (optional) React Error Boundary → Guide: **Error Handling & Resilience** ### 17. Visual Editor (optional) - Configure CSP headers: `frame-ancestors 'self' https://app.lynkow.com` - Add `` provider in layout - Mark editable fields with `data-lynkow-block` and `data-lynkow-field` - Use `useBlockData()` hook for live preview - Set Preview URL in admin: Settings > Site → Guide: **Visual Editor** ### 18. Draft Preview (optional) - Create preview entry route: `app/api/preview/route.ts` - Create exit route: `app/api/preview/exit/route.ts` - Fetch draft content via V1 API with Bearer token - Use `draftMode().isEnabled` to branch rendering → Guide: **Draft Preview** ### 19. Search (optional) - Server-side: `lynkow.search.query('term')` - Client-side: autocomplete component - Expose search endpoint for LLMs → Guide: **Instant Search** --- ## Admin Configuration Checklist These steps are done in the Lynkow admin dashboard, not in code. ### Site Setup - Site created with correct domain - Preview URL set (Settings > Site) — required for forms, reviews, visual editor, and localhost development - API Token created (Settings > API Tokens) — for webhooks and draft preview ### Content - At least one category created - Site Blocks created (header, footer, pages) - Blog articles published ### SEO - Sitemap settings configured (Settings > SEO) - Robots.txt rules set - llms.txt enabled with site description - (optional) Sitemap sources added for multi-site setup (Settings > SEO > Sitemap > External Sitemaps) - (optional) Redirections configured for URL changes (Settings > SEO > Redirects) ### Analytics & Consent - Analytics enabled - Consent mode set: **opt-in** for EU sites, **opt-out** for non-EU - Cookie consent categories configured (necessary, analytics, marketing) - (optional) Third-party scripts assigned to consent categories ### Webhooks - Webhook URL configured pointing to your `/api/revalidate` endpoint - Webhook secret set for HMAC-SHA256 verification - Events selected: content.published, content.updated, content.deleted ### Visual Editor - Preview URL set correctly (Settings > Site) - Site Blocks have schemas matching your `data-lynkow-block` slugs --- # SDK Types: Search **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Search ## `SearchConfig` *Interface* Configuration for client-side direct search. Returned by `lynkow.search.getConfig()`. Use these values to initialize a search client in the browser for instant autocomplete without round-tripping through your server. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `apiKey` | `string` | No | Short-lived tenant token (JWT, 1-hour expiry) scoped to your site's index | | `host` | `string` | No | Public search host URL (e.g. `'https://search.lynkow.com'`) | | `indexName` | `string` | No | Your site's search index name | --- ## `SearchHit` *Interface* A single search result (hit) returned by Lynkow Instant Search. Contains article metadata, URL path, and optional highlighted matches. Only published content appears in search results. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `_formatted` | `Record` | Yes | Highlighted matches with `` tags around matching terms.
Only present when the search engine returns formatted results.
Keys match the field names (e.g. `title`, `excerpt`, `body`). | | `authorName` | `string` | No | Full name of the content author | | `categories` | `{ name: string; slug: string }[]` | No | Categories assigned to this content | | `excerpt` | `string` | No | Short summary / excerpt | | `featuredImage` | `string \| null` | No | Featured image URL, or `null` if none | | `id` | `string` | No | Content UUID | | `locale` | `string` | No | Content locale code (e.g. `'fr'`, `'en'`) | | `metaDescription` | `string` | No | SEO meta description | | `metaTitle` | `string` | No | SEO meta title | | `path` | `string` | No | Full URL path including locale and category prefix (e.g. `'/fr/guides/forms'`) | | `publishedAt` | `number` | No | Publication date as Unix timestamp (seconds) | | `slug` | `string` | No | URL slug | | `tags` | `{ name: string; slug: string }[]` | No | Tags assigned to this content | | `title` | `string` | No | Article title | | `type` | `string` | No | Content type (e.g. `'post'`) | | `updatedAt` | `number` | No | Last update date as Unix timestamp (seconds) | --- ## `SearchOptions` *Interface* Options for `lynkow.search.search()`. All filters are optional. When omitted, searches across all published content in all locales. Extends: `BaseRequestOptions` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `category` | `string` | Yes | Filter by category slug (e.g. `'guides'`). Omit to search all categories. | | `fetchOptions` | `RequestInit` | Yes | Raw `fetch()` options merged into this specific request.
Useful for setting Next.js cache directives (`next: { revalidate: 60 }`)
or custom headers on a per-request basis.
These options are shallow-merged with the client-level `fetchOptions`. | | `limit` | `number` | Yes | Results per page (1--100). Defaults to `20`. | | `locale` | `string` | Yes | Filter by locale code (e.g. `'fr'`, `'en'`). Omit to search all locales. | | `page` | `number` | Yes | Page number (1-based). Defaults to `1`. | | `tag` | `string` | Yes | Filter by tag slug (e.g. `'featured'`). Omit to search all tags. | --- ## `SearchResponse` *Interface* Response from `lynkow.search.search()`. Contains an array of matching articles and pagination metadata. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `SearchHit[]` | No | Array of matching articles, ordered by relevance | | `meta` | `{ page: number; perPage: number; processingTimeMs: number; query: string; total: number; totalPages: number }` | No | Pagination and query metadata | --- # SDK Types: Routing **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Routing > **Related services:** **PathsService** — `lynkow.paths.list()`, `lynkow.paths.matchRedirect()`, `lynkow.paths.resolve()` ## `Path` *Interface* A renderable path for static site generation (SSG/ISR). Represents a published content article or category that has a public URL. Use `paths.list()` to fetch all paths, then generate static pages for each. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `lastModified` | `string` | No | ISO 8601 datetime string of the last modification.
Corresponds to the content's `updatedAt` or the category's `updatedAt`.
Useful for setting `lastmod` in XML sitemaps or `Last-Modified` headers. | | `locale` | `string` | No | Locale code for this path (e.g. `'en'`, `'fr'`).
Multi-locale sites generate separate paths for each locale.
Use this to set the page's `lang` attribute or route locale. | | `path` | `string` | No | Full URL path including leading slash and locale prefix.
Ready to use as a route path. Includes category segments in nested mode. | | `segments` | `string[]` | No | Individual path segments (split by `/`, without leading empty string).
Useful for Next.js `generateStaticParams()` catch-all routes. | | `type` | `"content" \| "category"` | No | The type of resource this path points to:
- `'content'`: a published blog article (resolve returns full Content)
- `'category'`: a category listing page (resolve returns CategoryDetail + paginated contents) | --- ## `Redirect` *Interface* A URL redirect rule configured in the admin dashboard. Returned by `paths.getRedirects()`. Apply these as server-side redirects in your framework's routing configuration. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `preserveQueryString` | `boolean` | No | Whether to append the original request's query string to the redirect target.
When `true`, a request to `/old?page=2` redirects to `/new?page=2`.
When `false`, the query string is discarded. | | `source` | `string` | No | Source path pattern to match incoming requests against.
Always starts with `/`. May contain path segments but not query strings. | | `statusCode` | `301 \| 302 \| 307 \| 308` | No | HTTP status code for the redirect:
- `301`: Permanent redirect (cached by browsers and search engines; passes link equity)
- `302`: Temporary redirect (not cached; used for A/B tests or maintenance)
- `307`: Temporary redirect preserving the HTTP method (POST stays POST)
- `308`: Permanent redirect preserving the HTTP method (POST stays POST)

Use `301` for SEO-friendly permanent moves; `302` for temporary changes. | | `target` | `string` | No | Destination path or full URL to redirect to.
Can be a relative path (starts with `/`) or an absolute URL. | --- # SDK Types: Reviews **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Reviews > **Related services:** **ReviewsService** — `lynkow.reviews.getBySlug()`, `lynkow.reviews.list()`, `lynkow.reviews.settings()`, `lynkow.reviews.submit()` ## `Review` *Interface* A customer review as returned by the public API. Only approved reviews are returned by public endpoints — pending and rejected reviews are never visible. Reviews are ordered by creation date (newest first) by default. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `authorEmail` | `string` | Yes | Email address of the review author.
May be partially masked for privacy (e.g. `'j***e@example.com'`) in public responses.
`undefined` if the email was not collected (depends on `ReviewSettings.fields.email`). | | `authorName` | `string` | No | Display name of the review author (e.g. `'Jane D.'`).
Min 2 characters, max 100 characters.
Always present — anonymous reviews still require a display name. | | `content` | `string` | No | The full review text body.
Min 10 characters, max 5000 characters. Plain text (no HTML).
Always present for approved reviews. | | `createdAt` | `string` | No | ISO 8601 datetime when the review was submitted (e.g. `'2024-03-15T10:30:00.000+00:00'`).
Set automatically on creation and never changes. | | `id` | `number` | No | Unique numeric review ID. Auto-incremented.
Use this for API calls like `GET /public/reviews/:id`. | | `locale` | `string` | No | BCP 47 locale code of this review (e.g. `'en'`, `'fr'`).
Determined by the locale parameter when the review was submitted,
or the site's default locale if none was specified. | | `rating` | `number` | No | Numeric rating given by the reviewer.
Integer between 1 and 5 (inclusive), where 1 is the lowest and 5 is the highest.
The allowed range is configured in `ReviewSettings.minRating` / `ReviewSettings.maxRating`
but the API always validates within 1-5. | | `response` | `ReviewResponse` | Yes | The site owner's response to this review, if one has been posted.
`undefined` if no response has been written yet.
Display this below the review to show the business's engagement. | | `slug` | `string \| null` | No | URL-friendly slug derived from the review title or author name.
`null` if no slug could be generated (e.g. if the title is empty and slugification failed).
Can be used for SEO-friendly review URLs on your frontend. | | `status` | `"approved"` | No | Review moderation status. Always `'approved'` via the public API.
Pending and rejected reviews are never returned by public endpoints. | | `title` | `string \| null` | No | Optional review title/headline (e.g. `'Great product!'`).
`null` if no title was provided. Max 200 characters.
Whether the title field is shown depends on `ReviewSettings.fields.title.enabled`. | --- ## `ReviewResponse` *Interface* An admin/site-owner response to a customer review. Displayed publicly below the review when present. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `content` | `string` | No | The response text as plain text.
Written by a site admin or owner in the admin dashboard.
Max 5000 characters. | | `respondedAt` | `string` | No | ISO 8601 datetime when the response was posted (e.g. `'2024-04-20T09:15:00.000+00:00'`).
Set automatically when the admin submits the response. | --- ## `ReviewSettings` *Interface* Public review settings for a site. Returned by `GET /public/reviews/settings`. Use these settings to: - Check if reviews are enabled before rendering a review form - Configure the rating input range (stars, slider, etc.) - Show/hide optional fields (title, email) in the submission form - Display moderation notices to users | Property | Type | Optional | Description | | --- | --- | --- | --- | | `allowAnonymous` | `boolean` | No | Whether anonymous reviews (without email) are accepted.
When `false`, the email field is mandatory regardless of `fields.email.required`.
When `true`, users can submit reviews without providing an email. | | `enabled` | `boolean` | No | Whether the review system is enabled for this site.
When `false`, don't render review forms or fetch reviews — the endpoints will return empty results. | | `fields` | `{ email: { enabled: boolean; required: boolean }; title: { enabled: boolean; required: boolean } }` | No | Field visibility and requirement configuration.
Use these to dynamically show/hide fields and mark them as required in your review form. | | `maxRating` | `number` | No | Maximum selectable rating value (inclusive). Integer between 1 and 5.
Use this to set the upper bound of your rating input (e.g. if set to 5, show stars 1-5).
The API rejects submissions with a rating above this value. | | `minRating` | `number` | No | Minimum selectable rating value (inclusive). Integer between 1 and 5.
Use this to set the lower bound of your rating input (e.g. if set to 1, show stars 1-5).
The API rejects submissions with a rating below this value. | | `requireApproval` | `boolean` | No | Whether new reviews require admin approval before appearing publicly.
When `true`, display a notice like "Your review will be visible after moderation"
after submission. When `false`, approved reviews appear immediately. | --- ## `ReviewSubmitData` *Interface* Data payload for submitting a new review via `POST /public/reviews`. Required fields: `authorName`, `rating`, `content`. Optional fields depend on `ReviewSettings.fields` configuration. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `authorEmail` | `string` | Yes | Email address of the reviewer.
Must be a valid email format. Not displayed publicly (or displayed masked).
Required when `ReviewSettings.allowAnonymous` is `false` or
`ReviewSettings.fields.email.required` is `true`.
Optional otherwise. | | `authorName` | `string` | No | Display name of the reviewer.
Min 2 characters, max 100 characters. Required.
Displayed publicly alongside the review. | | `content` | `string` | No | The full review text body. Min 10 characters, max 5000 characters.
Plain text only (no HTML). Required. | | `rating` | `number` | No | Numeric rating. Integer between 1 and 5 (inclusive).
Must be within the range defined by `ReviewSettings.minRating` and `ReviewSettings.maxRating`.
Required. | | `title` | `string` | Yes | Optional review title/headline. Max 200 characters.
Only include if `ReviewSettings.fields.title.enabled` is `true`.
Required when `ReviewSettings.fields.title.required` is `true`. | --- # SDK Types: Pages **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Pages > **Related services:** **PagesService** — `lynkow.pages.getByPath()`, `lynkow.pages.getBySlug()`, `lynkow.pages.getJsonLd()`, `lynkow.pages.list()` | **BlocksService** — `lynkow.blocks.getBySlug()`, `lynkow.blocks.global()`, `lynkow.blocks.siteConfig()` ## `Alternate` *Interface* An alternate language version of the same page. Used to build `` tags for multi-language SEO and language switcher UI components. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `current` | `boolean` | No | Whether this alternate represents the currently requested locale.
Exactly one alternate in the array will have `current: true`.
Useful for highlighting the active language in a language switcher. | | `locale` | `string` | No | BCP 47 locale code of this version (e.g. `'en'`, `'fr'`, `'de'`).
Matches one of the site's enabled locales. | | `path` | `string` | No | URL path to this locale's version of the page (e.g. `'/fr/about'`).
Does **not** include the domain — prepend your site's base URL.
`null` is never returned here; every alternate has a valid path. | --- ## `GlobalBlock` *Interface* A global block (reusable site-wide data object). Global blocks are schema-driven data containers used for site-wide elements like headers, footers, navigation menus, and banners. Their data is defined via a schema in the admin dashboard and can include DataSource references that are resolved server-side before delivery. Global blocks are typically fetched once at app initialization via `site.getConfig()` (which returns all globals) or individually via `blocks.getBySlug()`. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `_warnings` | `string[]` | Yes | Warnings generated during DataSource resolution.
Present only when one or more DataSources failed to resolve
(e.g. referenced content was deleted, DataSource query timed out).
When absent or empty, all data resolved successfully.
Each string describes the specific resolution failure. | | `data` | `Record` | No | Resolved block data.
Structure depends on the block's schema definition in the admin dashboard.
DataSource references are resolved server-side, so this object contains
actual data (e.g. arrays of content summaries, navigation links) rather
than DataSource identifiers.

Richtext fields within the data are automatically converted to HTML strings. | | `name` | `string` | No | Human-readable display name of the block, as set in the admin dashboard.
For UI/debugging purposes; use slug for lookups. | | `slug` | `string` | No | URL-friendly identifier for the block.
Unique within the site. Used to fetch individual blocks via `blocks.getBySlug()`.
Also used as the key in the `globals` map returned by `site.getConfig()`. | --- ## `LegalDocument` *Interface* A legal document (privacy policy, terms of service, cookie policy, etc.). Legal documents are implemented as pages tagged with `'legal'` in the CMS. They use the same schema-driven data model as regular pages but are surfaced through the dedicated `legal` service for convenience. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `Record` | No | Document content resolved from the page's schema.
Structure depends on how the page's schema was configured in the admin.
Richtext fields are automatically converted to HTML strings.
Typically contains a `'content'` or `'body'` key with the legal text as HTML. | | `id` | `string` | No | Unique identifier for the document.
Format varies by implementation (numeric string or UUID). | | `locale` | `string` | No | Locale code of this document version (e.g. `'en'`, `'fr'`).
Each locale has its own version of the legal document. | | `name` | `string` | No | Display name of the document (e.g. `'Privacy Policy'`, `'Terms of Service'`).
Localized -- changes based on the requested locale. | | `seo` | `{ description?: string \| null; ogImage?: string \| null; title?: string \| null } \| null` | Yes | SEO metadata for the document.
`null` when no SEO settings have been configured for this page. | | `slug` | `string` | No | URL-friendly slug used to fetch the document via `legal.getBySlug()`.
Typically descriptive: `'privacy-policy'`, `'terms-of-service'`, `'cookie-policy'`.
Unique within the site for a given locale. | | `updatedAt` | `string` | No | ISO 8601 datetime string of the last update.
Important for legal compliance -- display this to indicate when the document
was last revised (e.g. "Last updated: March 15, 2025"). | --- ## `Page` *Interface* Full page with resolved data sources, SEO settings, and locale alternates. Returned by single-page endpoints (`GET /public/pages/:idOrSlug`). Pages are layout-driven containers whose content comes from **DataSources** — configurable queries that fetch content, categories, forms, or other data. The API resolves all DataSources server-side and returns the results in `data`. Extends: `PageSummary` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `_warnings` | `string[]` | Yes | Warnings emitted during DataSource resolution.
Present only when one or more DataSources encountered non-fatal errors
(e.g. a referenced content was deleted, a query returned no results unexpectedly).
`undefined` when all DataSources resolved successfully.

These are informational — the page still renders, but some `data` keys may
contain empty arrays or `null` instead of the expected content.
Useful for debugging during development; avoid displaying these to end users. | | `alternates` | `Alternate[]` | No | Alternate language versions of this page.
Empty array if the site has only one locale or no translations exist.
Always includes the current locale (with `current: true`).
Use these to build `` tags
and language switcher navigation. | | `data` | `Record` | No | Resolved data from all configured DataSources, keyed by the DataSource name.

Each key corresponds to a DataSource defined in the page's schema.
The value type depends on the DataSource type:
- Content list: array of ContentSummary objects
- Single content: a Content object
- Category list: array of Category objects
- Form: a Form object
- Custom data: any JSON value | | `id` | `number` | No | Unique numeric page ID. Auto-incremented.
Use this for API calls like `GET /public/pages/:id`. | | `locale` | `string` | No | BCP 47 locale code of this page (e.g. `'en'`, `'fr'`).
Matches one of the site's enabled locales. | | `name` | `string` | No | Internal page name used for identification in the admin dashboard.
May differ from the SEO title. Max 255 characters. | | `path` | `string \| null` | No | URL path for this page, including locale prefix when applicable.
Format: `'/about'` (mono-language) or `'/en/about'` (multi-language).
`null` if the page has no path set (e.g. a page used only as a data container).
Does **not** include the domain — prepend your site's base URL. | | `seo` | `PageSeo \| null` | No | Page-level SEO settings for meta tags, Open Graph, and Twitter Card.
`null` if no SEO settings are configured for this page. | | `slug` | `string` | No | URL-friendly slug, unique within the site for this locale.
Lowercase, hyphenated (e.g. `'about-us'`). Max 255 characters.
Can be used instead of `id` in API calls: `GET /public/pages/:slug`. | | `updatedAt` | `string \| null` | No | ISO 8601 datetime of the last modification (e.g. `'2024-06-01T14:00:00.000+00:00'`).
Updated whenever the page content, data sources, or SEO settings are saved.
`null` if the page has never been modified after creation (rare edge case). | --- ## `PageSeo` *Interface* SEO settings for a page, controlling meta tags, Open Graph, Twitter Card, and technical SEO directives. All fields are optional — unset fields mean "use the default" or "don't render this tag". The frontend is responsible for rendering these values into the appropriate `` tags. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `canonicalUrl` | `string` | Yes | Override canonical URL for this page. Absolute URL.
`undefined` means the page's own URL is canonical.
Use when the same content is accessible at multiple URLs to avoid duplicate content penalties. | | `keywords` | `string[]` | Yes | Array of keyword strings for ``.
`undefined` means no keywords are set.
Note: most search engines no longer use this for ranking. | | `metaDescription` | `string` | Yes | `` value.
`undefined` means no meta description is set.
Recommended length: 150-160 characters for optimal search engine display. | | `metaTitle` | `string` | Yes | Custom `` and `<meta property="og:title">` value.<br>`undefined` means use the page name as fallback.<br>Recommended length: 50-60 characters for optimal search engine display. | | `noIndex` | `boolean` | Yes | When `true`, adds `<meta name="robots" content="noindex">` to prevent search engine indexing.<br>`undefined` or `false` means the page is indexable.<br>Use for staging pages, internal tools, or pages that should not appear in search results. | | `ogDescription` | `string` | Yes | `<meta property="og:description">` value.<br>`undefined` means fall back to `metaDescription`. | | `ogImage` | `SeoImage \| null` | Yes | Image for Open Graph sharing previews.<br>`null` means explicitly no image; `undefined` means not configured.<br>Recommended size: 1200x630px. | | `ogSiteName` | `string` | Yes | `<meta property="og:site_name">` value.<br>`undefined` means use the site's name from site settings. | | `ogTitle` | `string` | Yes | `<meta property="og:title">` value.<br>`undefined` means fall back to `metaTitle`, then the page name. | | `ogUrl` | `string` | Yes | `<meta property="og:url">` value. Absolute URL.<br>`undefined` means use the canonical URL or the page's own URL. | | `twitterCard` | `"summary" \| "summary_large_image" \| "app" \| "player"` | Yes | `<meta name="twitter:card">` value controlling the Twitter Card layout:<br>- `'summary'`: small square image with title and description<br>- `'summary_large_image'`: large banner image above title and description<br>- `'app'`: app install card (rarely used for websites)<br>- `'player'`: embedded media player<br><br>`undefined` defaults to `'summary_large_image'` if an image is present. | | `twitterDescription` | `string` | Yes | `<meta name="twitter:description">` value.<br>`undefined` means fall back to `ogDescription`, then `metaDescription`. | | `twitterImage` | `SeoImage \| null` | Yes | Image for Twitter Card previews.<br>`null` means explicitly no image; `undefined` means fall back to `ogImage`.<br>Recommended size: 1200x628px for `summary_large_image`, 144x144px for `summary`. | | `twitterTitle` | `string` | Yes | `<meta name="twitter:title">` value.<br>`undefined` means fall back to `ogTitle`, then `metaTitle`, then the page name. | --- ## `PageSummary` *Interface* Lightweight page representation returned in list endpoints. Contains basic identification and routing information. Use Page (from `GET /public/pages/:idOrSlug`) for the full page including resolved data, SEO settings, and alternates. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `id` | `number` | No | Unique numeric page ID. Auto-incremented.<br>Use this for API calls like `GET /public/pages/:id`. | | `locale` | `string` | No | BCP 47 locale code of this page (e.g. `'en'`, `'fr'`).<br>Matches one of the site's enabled locales. | | `name` | `string` | No | Internal page name used for identification in the admin dashboard.<br>May differ from the SEO title. Max 255 characters. | | `path` | `string \| null` | No | URL path for this page, including locale prefix when applicable.<br>Format: `'/about'` (mono-language) or `'/en/about'` (multi-language).<br>`null` if the page has no path set (e.g. a page used only as a data container).<br>Does **not** include the domain — prepend your site's base URL. | | `slug` | `string` | No | URL-friendly slug, unique within the site for this locale.<br>Lowercase, hyphenated (e.g. `'about-us'`). Max 255 characters.<br>Can be used instead of `id` in API calls: `GET /public/pages/:slug`. | | `updatedAt` | `string \| null` | No | ISO 8601 datetime of the last modification (e.g. `'2024-06-01T14:00:00.000+00:00'`).<br>Updated whenever the page content, data sources, or SEO settings are saved.<br>`null` if the page has never been modified after creation (rare edge case). | --- # SDK Types: Images & Media **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Images & Media > **Related services:** **MediaHelperService** — `lynkow.mediaHelper.srcset()`, `lynkow.mediaHelper.transform()` ## `ImageVariants` *Interface* CDN image variant URLs generated by Cloudflare Image Transformations. Each property is a pre-generated URL for a specific size/crop preset. All URLs point to Cloudflare's CDN and support WebP/AVIF auto-negotiation via the `Accept` header. Properties are `undefined` when the original image is smaller than the preset dimensions (no upscaling is performed). Use the smallest variant that fits your layout to minimize bandwidth. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `avatar` | `string` | Yes | 128x128px, cover crop with face detection gravity.<br>Use case: user avatars, author photos, profile pictures.<br>Face detection ensures faces are centered in the crop.<br>`undefined` when the original image is smaller than 128x128. | | `card` | `string` | Yes | 600x400px, cover crop.<br>Use case: card layouts, blog post previews, medium-sized list items.<br>`undefined` when the original image is smaller than 600x400. | | `content` | `string` | Yes | 1200px wide, scaled down proportionally (no crop).<br>Use case: inline images within article body content.<br>`undefined` when the original image is narrower than 1200px. | | `full` | `string` | Yes | 2560px wide, scaled down proportionally (no crop).<br>Use case: full-resolution display, lightbox/zoom views, retina hero images.<br>This is the largest available variant. `undefined` when the original<br>image is narrower than 2560px. | | `hero` | `string` | Yes | 1920px wide, scaled down proportionally (no crop).<br>Use case: full-width hero banners, page headers, background images.<br>`undefined` when the original image is narrower than 1920px. | | `medium` | `string` | Yes | 960px wide, scaled down proportionally (no crop).<br>Use case: medium-width displays, sidebar featured images, tablet layouts.<br>`undefined` when the original image is narrower than 960px. | | `og` | `string` | Yes | 1200x630px, cover crop.<br>Use case: Open Graph images, social media sharing (Facebook, LinkedIn, Twitter).<br>Dimensions follow the recommended OG image ratio (1.91:1).<br>`undefined` when the original image is smaller than 1200x630. | | `thumbnail` | `string` | Yes | 400x300px, cover crop with sharpening.<br>Use case: list views, grid thumbnails, small cards.<br>`undefined` when the original image is smaller than 400x300. | --- ## `SrcsetOptions` *Interface* Options for building srcset URLs | Property | Type | Optional | Description | | --- | --- | --- | --- | | `fit` | `"cover" \| "contain" \| "scale-down" \| "crop"` | Yes | Resize fit mode (default: 'scale-down') | | `gravity` | `string` | Yes | Focal point for crop (e.g., '0.5x0.3') | | `quality` | `number` | Yes | Image quality 1-100 (default: 80) | | `widths` | `number[]` | Yes | Image widths to include in srcset (default: [400, 800, 1200, 1920]) | --- ## `TransformOptions` *Interface* Options for building a single transformed URL | Property | Type | Optional | Description | | --- | --- | --- | --- | | `dpr` | `number` | Yes | Device Pixel Ratio 1-4 | | `fit` | `"cover" \| "contain" \| "scale-down" \| "crop"` | Yes | Resize fit mode | | `format` | `"auto" \| "webp" \| "avif" \| "jpeg"` | Yes | Output format (default: 'auto') | | `gravity` | `string` | Yes | Focal point for crop | | `h` | `number` | Yes | Target height in pixels | | `quality` | `number` | Yes | Image quality 1-100 (default: 80) | | `w` | `number` | Yes | Target width in pixels | --- # SDK Types: Forms **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Forms > **Related services:** **FormsService** — `lynkow.forms.getBySlug()`, `lynkow.forms.submit()` ## `Form` *Interface* A public form available for submission. Returned by `GET /public/forms/:slug`. Use `schema` to dynamically render the form fields, `settings` to configure the submit button and post-submission behavior, and the spam protection fields to set up honeypot or reCAPTCHA if enabled. Only forms with `status: 'active'` are returned by the public API. Draft, closed, and archived forms are never returned. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `description` | `string \| null` | No | Optional form description, displayed above the form fields.<br>`null` if not set. Max 1000 characters.<br>Can be used as introductory text or instructions for the user. | | `honeypotEnabled` | `boolean` | Yes | Whether honeypot spam protection is enabled for this form.<br><br>When `true`, your frontend **must** render a hidden honeypot field<br>(invisible to humans, filled by bots). The SDK handles this automatically<br>via `lynkow.forms.submit()`, but if you submit manually, include an empty<br>hidden field named per the spam protection API specification.<br><br>`undefined` or `false` means honeypot is not active. | | `id` | `number` | No | Unique numeric form ID. Auto-incremented. | | `locale` | `string` | Yes | BCP 47 locale code of this form (e.g. `'en'`, `'fr'`).<br>`undefined` if the form is not locale-specific (shared across all locales). | | `name` | `string` | No | Form display name (e.g. `'Contact Us'`, `'Newsletter Signup'`). Max 255 characters. | | `recaptchaEnabled` | `boolean` | Yes | Whether Google reCAPTCHA v3 is enabled for this form.<br><br>When `true`, your frontend must:<br>1. Load the reCAPTCHA script using `recaptchaSiteKey`<br>2. Execute `grecaptcha.execute()` before submission<br>3. Include the reCAPTCHA token in the submission payload<br><br>The SDK handles this automatically if you use `lynkow.forms.submit()`.<br>Cannot be combined with honeypot — only one spam protection method is active.<br><br>`undefined` or `false` means reCAPTCHA is not active. | | `recaptchaSiteKey` | `string \| null` | Yes | Google reCAPTCHA v3 public site key, needed to render the reCAPTCHA widget.<br>Only present when `recaptchaEnabled` is `true`.<br>`null` or `undefined` when reCAPTCHA is not configured.<br><br>Pass this to `grecaptcha.render()` or the `sitekey` parameter in the reCAPTCHA script URL.<br>The secret key is never exposed — it stays server-side. | | `schema` | `FormField[]` | No | Ordered array of field definitions that make up the form.<br>Render these in order to build the form UI dynamically. | | `settings` | `FormSettings` | No | Form behavior settings (submit button text, success message, redirect). | | `slug` | `string` | No | URL-friendly slug, unique within the site.<br>Lowercase, hyphenated (e.g. `'contact-us'`). Max 255 characters.<br>Used to fetch the form: `GET /public/forms/:slug`.<br>Also used as the submission endpoint: `POST /public/forms/:slug/submit`. | | `status` | `"active"` | No | Form status. Always `'active'` via the public API.<br>Draft, closed, and archived forms are never returned by public endpoints. | --- ## `FormField` *Interface* A single field definition in a form schema. Describes the input type, label, validation, and layout for one form field. Use these to dynamically render form fields in your frontend. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `defaultValue` | `string \| number \| boolean` | Yes | Pre-filled default value for the field.<br>Type depends on the field type: `string` for text fields, `number` for number fields,<br>`boolean` for checkbox (single toggle mode).<br>`undefined` if no default is set — the field starts empty. | | `helpText` | `string` | Yes | Help text displayed below the input to guide the user.<br>Max 500 characters. `undefined` if not configured.<br>Render as a small description below the field (e.g. `<small>` or `aria-describedby`). | | `label` | `string` | No | Human-readable label displayed above or beside the input.<br>Max 255 characters. Always present — use this as the `<label>` text. | | `name` | `string` | No | Field identifier, used as the key in submission data.<br>Unique within the form. Lowercase, alphanumeric with underscores or hyphens<br>(e.g. `'first_name'`, `'email'`). Max 100 characters.<br>This is the key you use in `FormSubmitData` when submitting. | | `options` | `FormFieldOption[]` | Yes | Selectable options for `select`, `radio`, and `checkbox` field types.<br>`undefined` for field types that don't use options (text, email, number, etc.).<br>For `checkbox` with options, render as a checkbox group (multiple selections).<br>For `checkbox` without options, render as a single boolean toggle. | | `placeholder` | `string` | Yes | Placeholder text shown inside the input when empty.<br>Max 255 characters. `undefined` if not configured.<br>Use as the HTML `placeholder` attribute. | | `required` | `boolean` | No | Whether this field must be filled before submission.<br>When `true`, the server will reject submissions with this field empty.<br>Render a required indicator (e.g. `*`) and set the HTML `required` attribute. | | `type` | `FormFieldType` | No | HTML input type for this field.<br>Determines the rendered input element and applicable validation rules. | | `validation` | `FormFieldValidation` | Yes | Validation rules applied to this field's value.<br>`undefined` if no extra validation beyond `required` is configured.<br>Rules are enforced server-side — client-side validation is optional but recommended for UX. | | `width` | `"full" \| "half" \| "third"` | Yes | Layout width hint for responsive form layouts:<br>- `'full'`: spans the entire form width (100%)<br>- `'half'`: spans half the form width (50%) — place two `half` fields side by side<br>- `'third'`: spans one third of the form width (33%) — place three `third` fields in a row<br><br>`undefined` defaults to `'full'`. | --- ## `FormFieldOption` *Interface* A selectable option for `select`, `radio`, and `checkbox` field types. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `label` | `string` | No | Human-readable label displayed to the user in the form UI.<br>May differ from `value` (e.g. label: `'United States'`, value: `'US'`). | | `value` | `string` | No | The value sent in the form submission data.<br>This is the machine-readable identifier stored in the database.<br>Must be unique within the field's options array. | --- ## `FormFieldType` *TypeAlias* Supported input types for form fields. Maps to standard HTML input types, with a few additions: - `'textarea'`: multi-line text input (`<textarea>`) - `'select'`: dropdown menu (`<select>`) — requires `options` - `'radio'`: radio button group — requires `options` - `'checkbox'`: checkbox or checkbox group — uses `options` if present, otherwise single boolean - `'file'`: file upload input — accepts files via multipart form submission - `'hidden'`: hidden input — not displayed to users, useful for tracking parameters ```typescript type FormFieldType = "text" | "email" | "tel" | "url" | "textarea" | "number" | "date" | "datetime" | "time" | "select" | "radio" | "checkbox" | "file" | "hidden" ``` --- ## `FormFieldValidation` *Interface* Validation rules for a form field. Applied both client-side (for UX) and server-side (for security). Which rules are relevant depends on the field type: - `minLength`/`maxLength`: text-based fields (`text`, `email`, `textarea`, `url`, `tel`) - `min`/`max`: `number` fields only - `pattern`: any text-based field — validated as a JavaScript RegExp | Property | Type | Optional | Description | | --- | --- | --- | --- | | `max` | `number` | Yes | Maximum numeric value (inclusive).<br>Only applicable to `number` field type.<br>Ignored for text fields — use `maxLength` instead. | | `maxLength` | `number` | Yes | Maximum character length for text input.<br>Applicable to `text`, `email`, `textarea`, `url`, `tel` field types.<br>Ignored for non-text fields. | | `message` | `string` | Yes | Custom error message displayed when validation fails.<br>`undefined` means the browser/framework default message is used.<br>Supports the field's locale if i18n is configured. | | `min` | `number` | Yes | Minimum numeric value (inclusive).<br>Only applicable to `number` field type.<br>Ignored for text fields — use `minLength` instead. | | `minLength` | `number` | Yes | Minimum character length for text input.<br>Applicable to `text`, `email`, `textarea`, `url`, `tel` field types.<br>Ignored for non-text fields (`number`, `select`, `checkbox`, etc.). | | `pattern` | `string` | Yes | Regular expression pattern for custom validation (e.g. `'^[A-Z]{2}\\d{4}$'`).<br>Validated as a JavaScript RegExp. Do not include delimiters (no `/pattern/`).<br>Applicable to any text-based field type. | --- ## `FormSettings` *Interface* Form behavior settings controlling what happens after submission. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `doubleOptIn` | `boolean` | Yes | Whether double opt-in email confirmation is enabled.<br>When `true`, the form sends a confirmation email to the user before<br>recording the submission as confirmed. Useful for newsletter signups.<br>`undefined` or `false` means submissions are recorded immediately. | | `redirectUrl` | `string` | Yes | URL to redirect the user to after successful submission.<br>Max 500 characters. Must be a valid URL (e.g. `'https://example.com/thank-you'`).<br>When set, takes precedence over `successMessage` — the user is navigated away immediately.<br>`undefined` means show `successMessage` inline instead. | | `submitLabel` | `string` | No | Text displayed on the submit button (e.g. `'Send'`, `'Subscribe'`, `'Submit'`).<br>Max 100 characters. Always present. | | `successMessage` | `string` | No | Success message displayed after a successful submission.<br>Max 1000 characters. Shown inline on the page.<br><br>Ignored if `redirectUrl` is set — the user is redirected instead of seeing this message. | --- ## `FormSubmitData` *TypeAlias* Data payload for submitting a form. Keys correspond to `FormField.name` values from the form's `schema`. Value types depend on the field type: - Text fields (`text`, `email`, `textarea`, `url`, `tel`): `string` - Number fields: `number` - Checkbox (single): `boolean` - File fields: `File` (browser File object) - Select/radio/checkbox (with options): `string` (the selected option's `value`) All fields marked as `required: true` in the schema must be present. Omit optional fields or set them to empty string. ```typescript type FormSubmitData = Record<string, string | number | boolean | File> ``` --- # SDK Types: Filters & Options **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Filters & Options ## `BaseRequestOptions` *Interface* Base request options available on every SDK method. These override the defaults set in LynkowConfig. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `fetchOptions` | `RequestInit` | Yes | Raw `fetch()` options merged into this specific request.<br>Useful for setting Next.js cache directives (`next: { revalidate: 60 }`)<br>or custom headers on a per-request basis.<br>These options are shallow-merged with the client-level `fetchOptions`. | | `locale` | `string` | Yes | Locale override for this specific request.<br>When set, overrides the `locale` configured on the client instance.<br>Must be a locale code enabled on the site (e.g. `'en'`, `'fr'`, `'de'`).<br>If omitted, the client's default locale is used; if that is also unset,<br>the site's default locale applies. | --- ## `CategoryOptions` *Interface* Options for the `categories.getBySlug()` endpoint. Controls pagination of the contents nested within the category response. Extends: `PaginationOptions` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `limit` | `number` | Yes | Maximum number of items per page.<br>Defaults to 15. Maximum allowed value is 100.<br>Values above 100 are silently capped by the API. | | `locale` | `string` | Yes | Locale override for this request.<br>Returns the category and its contents in the specified locale.<br>When omitted, uses the client's default locale or the site's default locale. | | `page` | `number` | Yes | 1-based page number to retrieve.<br>Defaults to 1. Values less than 1 are treated as 1.<br>Requesting a page beyond the last page returns an empty `data` array. | | `perPage` | `number` | Yes | Alias for limit. Maximum number of items per page.<br>When both `limit` and `perPage` are provided, `limit` takes precedence. | --- ## `ContentsFilters` *Interface* Filters for the `contents.list()` endpoint. All filters are optional and can be combined. When multiple filters are provided, they are applied with AND logic (all conditions must match). Extends: `PaginationOptions`, `SortOptions` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `category` | `string` | Yes | Filter contents by category slug (not ID, not name).<br>Only returns contents that belong to the specified category. | | `limit` | `number` | Yes | Maximum number of items per page.<br>Defaults to 15. Maximum allowed value is 100.<br>Values above 100 are silently capped by the API. | | `locale` | `string` | Yes | Locale override for this request.<br>Only returns contents in the specified locale.<br>When omitted, uses the client's default locale or the site's default locale. | | `order` | `"asc" \| "desc"` | Yes | Sort direction.<br>- `'asc'`: ascending (oldest first, lowest rating first, A-Z)<br>- `'desc'`: descending (newest first, highest rating first, Z-A)<br><br>Defaults to `'desc'`. | | `page` | `number` | Yes | 1-based page number to retrieve.<br>Defaults to 1. Values less than 1 are treated as 1.<br>Requesting a page beyond the last page returns an empty `data` array. | | `perPage` | `number` | Yes | Alias for limit. Maximum number of items per page.<br>When both `limit` and `perPage` are provided, `limit` takes precedence. | | `search` | `string` | Yes | Full-text search query against content title and excerpt.<br>The search is case-insensitive and uses partial matching (LIKE).<br>Special characters in the query are escaped automatically.<br>An empty string is ignored. | | `sort` | `string` | Yes | Field name to sort by.<br>Valid values depend on the endpoint:<br>- Contents: `'title'`, `'created_at'`, `'published_at'`, `'updated_at'`<br>- Reviews: `'created_at'`, `'rating'`<br>- Categories: `'name'`, `'created_at'`, `'content_count'`<br><br>Defaults to `'published_at'` for contents, `'created_at'` for reviews. | | `tag` | `string` | Yes | Filter contents by tag slug (not ID, not name).<br>Only returns contents that have the specified tag attached. | --- ## `PaginationOptions` *Interface* Pagination options for list endpoints. All values are 1-based integers. The API defaults to page 1 with 15 items per page. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `limit` | `number` | Yes | Maximum number of items per page.<br>Defaults to 15. Maximum allowed value is 100.<br>Values above 100 are silently capped by the API. | | `page` | `number` | Yes | 1-based page number to retrieve.<br>Defaults to 1. Values less than 1 are treated as 1.<br>Requesting a page beyond the last page returns an empty `data` array. | | `perPage` | `number` | Yes | Alias for limit. Maximum number of items per page.<br>When both `limit` and `perPage` are provided, `limit` takes precedence. | --- ## `ReviewsFilters` *Interface* Filters for the `reviews.list()` endpoint. Only approved reviews are returned; these filters further narrow the result set. Extends: `PaginationOptions`, `SortOptions` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `limit` | `number` | Yes | Maximum number of items per page.<br>Defaults to 15. Maximum allowed value is 100.<br>Values above 100 are silently capped by the API. | | `maxRating` | `number` | Yes | Maximum rating (inclusive) to filter by.<br>Integer between 1 and 5. Reviews with a rating above this value are excluded.<br>Must be greater than or equal to minRating when both are provided. | | `minRating` | `number` | Yes | Minimum rating (inclusive) to filter by.<br>Integer between 1 and 5. Reviews with a rating below this value are excluded.<br>Must be less than or equal to maxRating when both are provided. | | `order` | `"asc" \| "desc"` | Yes | Sort direction.<br>- `'asc'`: ascending (oldest first, lowest rating first, A-Z)<br>- `'desc'`: descending (newest first, highest rating first, Z-A)<br><br>Defaults to `'desc'`. | | `page` | `number` | Yes | 1-based page number to retrieve.<br>Defaults to 1. Values less than 1 are treated as 1.<br>Requesting a page beyond the last page returns an empty `data` array. | | `perPage` | `number` | Yes | Alias for limit. Maximum number of items per page.<br>When both `limit` and `perPage` are provided, `limit` takes precedence. | | `sort` | `string` | Yes | Field name to sort by.<br>Valid values depend on the endpoint:<br>- Contents: `'title'`, `'created_at'`, `'published_at'`, `'updated_at'`<br>- Reviews: `'created_at'`, `'rating'`<br>- Categories: `'name'`, `'created_at'`, `'content_count'`<br><br>Defaults to `'published_at'` for contents, `'created_at'` for reviews. | --- ## `SortOptions` *Interface* Sort options for list endpoints. Not all fields are sortable on every endpoint -- invalid sort fields are ignored. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `order` | `"asc" \| "desc"` | Yes | Sort direction.<br>- `'asc'`: ascending (oldest first, lowest rating first, A-Z)<br>- `'desc'`: descending (newest first, highest rating first, Z-A)<br><br>Defaults to `'desc'`. | | `sort` | `string` | Yes | Field name to sort by.<br>Valid values depend on the endpoint:<br>- Contents: `'title'`, `'created_at'`, `'published_at'`, `'updated_at'`<br>- Reviews: `'created_at'`, `'rating'`<br>- Categories: `'name'`, `'created_at'`, `'content_count'`<br><br>Defaults to `'published_at'` for contents, `'created_at'` for reviews. | --- ## `SubmitOptions` *Interface* Options for form and review submission endpoints. Used to pass spam protection tokens alongside the submission data. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `recaptchaToken` | `string` | Yes | Google reCAPTCHA v3 token for spam protection.<br>Required when the form/site has reCAPTCHA enabled (check `form.recaptchaEnabled`).<br>Obtained client-side via `grecaptcha.execute(siteKey, { action: 'submit' })`.<br>When reCAPTCHA is not enabled on the form, this field is ignored.<br>The site key for rendering the reCAPTCHA widget is available at `form.recaptchaSiteKey`. | --- # SDK Types: Errors **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Errors ## `ApiErrorDetail` *Interface* A single error detail from the API's response body. The API returns an array of these in the `errors` field of error responses. For validation errors, each detail corresponds to one invalid field. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `code` | `string` | Yes | Machine-readable error rule or code from the validation layer.<br>Can be used for programmatic error handling or i18n error message lookups.<br>`undefined` when the API does not provide a structured code. | | `field` | `string` | Yes | Name of the form/request field that caused the validation error.<br>Only present for `VALIDATION_ERROR` responses.<br>`undefined` for non-field-specific errors (e.g. auth, rate limiting).<br>Use this to display inline validation errors next to form fields. | | `message` | `string` | No | Human-readable error message describing what went wrong.<br>Suitable for display to end users or logging. | --- ## `ErrorCode` *TypeAlias* Error codes for Lynkow SDK errors. Each code maps to a specific category of failure. Use these in `catch` blocks to handle different error types: | Code | HTTP Status | When it occurs | | --- | --- | --- | | `BAD_REQUEST` | 400 | Malformed request (missing required fields, bad format) | | `VALIDATION_ERROR` | 400 | Request body fails server-side validation (see `details`) | | `UNAUTHORIZED` | 401 | Missing or invalid authentication (bad site ID) | | `FORBIDDEN` | 403 | Valid auth but insufficient permissions | | `NOT_FOUND` | 404 | Resource does not exist or is not published | | `RATE_LIMITED` | 429 | Too many requests; retry after the indicated delay | | `TOO_MANY_REQUESTS` | 429 | Alias for `RATE_LIMITED` | | `TIMEOUT` | - | Request was aborted due to timeout (client-side) | | `NETWORK_ERROR` | - | DNS failure, connection refused, or network offline | | `INTERNAL_ERROR` | 500 | Server-side error (bug or infrastructure issue) | | `SERVICE_UNAVAILABLE` | 503 | API is temporarily down for maintenance | | `UNKNOWN` | other | Unrecognized HTTP status or unexpected error | ```typescript type ErrorCode = "BAD_REQUEST" | "VALIDATION_ERROR" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "RATE_LIMITED" | "TOO_MANY_REQUESTS" | "TIMEOUT" | "NETWORK_ERROR" | "INTERNAL_ERROR" | "SERVICE_UNAVAILABLE" | "UNKNOWN" ``` --- # SDK Types: Cookies & Consent **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Cookies & Consent > **Related services:** **CookiesService** — `lynkow.cookies.getConfig()`, `lynkow.cookies.logConsent()` | **ConsentService** — `lynkow.consent.acceptAll()`, `lynkow.consent.destroy()`, `lynkow.consent.getCategories()`, `lynkow.consent.getConfig()`, `lynkow.consent.hasConsented()`, `lynkow.consent.hide()`, `lynkow.consent.logConsent()`, `lynkow.consent.rejectAll()`, `lynkow.consent.reset()`, `lynkow.consent.setCategories()`, `lynkow.consent.show()`, `lynkow.consent.showPreferences()` ## `ConsentCategories` *Interface* Consent categories | Property | Type | Optional | Description | | --- | --- | --- | --- | | `analytics` | `boolean` | No | Analytics cookies | | `marketing` | `boolean` | No | Marketing cookies | | `necessary` | `boolean` | No | Always true, not modifiable | | `preferences` | `boolean` | No | Preference cookies | --- ## `CookieCategory` *Interface* A cookie consent category grouping related cookies and scripts. Users accept or reject entire categories (not individual cookies). | Property | Type | Optional | Description | | --- | --- | --- | --- | | `description` | `string` | No | Description explaining what cookies in this category do.<br>Displayed in the consent banner's detailed/customize view. | | `id` | `string` | No | Machine-readable category identifier.<br>Standard values: `'necessary'`, `'analytics'`, `'marketing'`, `'preferences'`.<br>Used as the key in CookiePreferences when recording consent. | | `name` | `string` | No | Human-readable category name displayed in the consent banner. | | `required` | `boolean` | No | Whether this category is required and cannot be toggled off by the user.<br>Always `true` for the `'necessary'` category (essential cookies).<br>When `true`, the toggle is disabled/checked in the consent UI.<br>When `false`, the user can opt in or out of this category. | --- ## `CookieConfig` *Interface* Full cookie consent banner configuration. Returned by `cookies.getConfig()`. Contains all settings needed to render and manage a GDPR-compliant consent banner. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `borderRadius` | `number` | Yes | Border radius in pixels for the banner container and buttons.<br>Use `0` for sharp corners, `8`-`12` for rounded, `9999` for pill-shaped buttons. | | `categories` | `CookieCategory[]` | No | Cookie categories that users can accept or reject.<br>Always includes at least a `'necessary'` category with `required: true`.<br>Order matches the display order in the preferences UI. | | `consentMode` | `"opt-in" \| "opt-out"` | Yes | How consent is handled:<br>- `'opt-in'`: all non-necessary cookies are blocked until the user explicitly accepts.<br> This is the GDPR-compliant default for EU sites.<br>- `'opt-out'`: all cookies are active by default; user can reject them.<br> Less restrictive, suitable for non-EU sites.<br><br>Only present when enabled is `true`. | | `cookiePolicyUrl` | `string \| null` | Yes | URL to a dedicated cookie policy page (separate from the privacy policy).<br>`null` when no cookie policy URL has been configured.<br>Some sites use a single privacy policy; others maintain a separate cookie policy. | | `enabled` | `boolean` | No | Whether the cookie consent banner is enabled for this site.<br>When `false`, no banner should be shown and no consent logic is needed. | | `fontSize` | `number` | Yes | Font size in pixels for the banner text.<br>Constrained to the range 11-17. Values outside this range are clamped. | | `layout` | `"floating"` | No | Banner layout style. Currently only `'floating'` is supported<br>(a dismissable card overlay, not a full-width bar). | | `position` | `"bottom-left" \| "bottom-right"` | No | Screen position of the floating consent banner.<br>- `'bottom-left'`: anchored to the bottom-left corner<br>- `'bottom-right'`: anchored to the bottom-right corner | | `primaryColor` | `string` | Yes | Primary accent color for buttons and interactive elements.<br>Format: 6-digit hex with `#` prefix (e.g. `'#3b82f6'`).<br>Applied to the "Accept All" button and toggle switches. | | `privacyPolicyUrl` | `string \| null` | Yes | URL to the site's privacy policy page.<br>`null` when no privacy policy URL has been configured.<br>Should be rendered as a clickable link in the banner. | | `showCustomizeButton` | `boolean` | Yes | Whether to show the "Customize" button that lets users pick categories individually.<br>When `false`, only "Accept All" and "Reject All" buttons are shown. | | `showOnFirstVisit` | `boolean` | Yes | Whether to automatically show the banner on the user's first visit.<br>When `false`, the banner must be triggered manually (e.g. via a footer link).<br>Only present when enabled is `true`. | | `texts` | `CookieTexts` | No | Localized text strings for the banner UI.<br>All strings are ready for display without further formatting. | | `theme` | `"light" \| "dark" \| "auto"` | No | Color theme for the banner:<br>- `'light'`: white/light background with dark text<br>- `'dark'`: dark background with light text<br>- `'auto'`: follows the user's OS/browser color scheme preference<br><br>When `'auto'`, use themeStyles to get the exact colors for each scheme. | | `themeStyles` | `{ dark: { bgColor: string; primaryColor: string; textColor: string }; light: { bgColor: string; primaryColor: string; textColor: string } }` | Yes | Per-theme color overrides for light and dark modes.<br>Only relevant when theme is `'auto'`.<br>When theme is `'light'` or `'dark'`, use the corresponding sub-object. | | `thirdPartyScripts` | `ThirdPartyScript[]` | Yes | Third-party scripts to inject based on the user's consent preferences.<br>Each script is tied to a category; only inject scripts whose category<br>the user has accepted. Empty array when no scripts are configured. | --- ## `CookiePreferences` *TypeAlias* Map of cookie category IDs to the user's consent decision. `true` means the user accepted that category; `false` means rejected. Used when calling `cookies.logConsent()` to record preferences. ```typescript type CookiePreferences = Record<string, boolean> ``` --- ## `CookieTexts` *Interface* Localized text strings for the cookie consent banner. All fields are pre-configured by the site admin and ready for display. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `acceptAll` | `string` | No | Label for the "Accept All" button that grants consent for all categories. | | `customize` | `string` | No | Label for the "Customize" button that opens the detailed category preferences. | | `description` | `string` | No | Main description/message shown in the cookie banner.<br>Typically explains that the site uses cookies and links to the privacy policy. | | `privacyPolicy` | `string` | No | Link text pointing to the privacy/cookie policy page. | | `rejectAll` | `string` | No | Label for the "Reject All" button that only allows necessary cookies. | | `save` | `string` | No | Label for the "Save" button in the detailed preferences view.<br>Saves the user's per-category choices. | --- # SDK Types: Content Schema **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Content Schema ## `ContentSchema` *Interface* Complete content schema defining all custom fields for a structured category. Attached to categories with `contentMode: 'structured'`. Content articles in structured categories populate their `customData` field according to this schema instead of using the free-form `body` editor. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `fields` | `SchemaField[]` | No | Ordered array of field definitions.<br>Fields are rendered in the admin editor in the order they appear here.<br>Each field's `key` becomes a property in the content's `customData` object. | --- ## `SchemaField` *Interface* A single field definition in a content schema. Defines the structure, constraints, and display properties of one custom field. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `defaultValue` | `unknown` | Yes | Default value pre-filled when creating new content.<br>The type of this value should match the field's `type`<br>(e.g. `string` for text fields, `number` for number fields, `boolean` for boolean fields).<br>When not specified, the field starts empty. | | `fields` | `SchemaField[]` | Yes | Sub-field definitions for `object` and `array` field types.<br>Required when `type` is `'object'` or `'array'`; ignored for other types.<br><br>- For `'object'`: defines the properties of the nested object.<br>- For `'array'`: defines the properties of each item in the array.<br><br>Sub-fields can themselves contain nested `fields` for deeply structured data. | | `helpText` | `string` | Yes | Help text displayed below the field in the admin editor.<br>Used to provide additional context or instructions to content editors. | | `itemLabel` | `string` | Yes | Display label for individual items in an `array` field.<br>Shown in the admin editor's repeater UI (e.g. "Slide", "Feature", "FAQ Item").<br>Only used when `type` is `'array'`. Ignored for other types. | | `key` | `string` | No | Unique field identifier used as the property name in the content's `customData` object.<br>Must be a valid JavaScript identifier: lowercase letters, numbers, and underscores only.<br>No spaces or hyphens allowed. Must be unique within the schema. | | `label` | `string` | No | Human-readable label displayed in the admin editor above the field.<br>Can contain spaces and special characters. Used for UI only, not in the data. | | `maxItems` | `number` | Yes | Maximum number of items allowed in an `array` field (inclusive).<br>Only used when `type` is `'array'`. Ignored for other types.<br>The admin editor prevents adding items beyond this limit. | | `minItems` | `number` | Yes | Minimum number of items required in an `array` field (inclusive).<br>Only used when `type` is `'array'`. Ignored for other types.<br>Validation fails if fewer items are provided. | | `options` | `SchemaFieldOption[]` | Yes | Available options for `select` and `multiselect` field types.<br>Required when `type` is `'select'` or `'multiselect'`; ignored for other types.<br>Each option defines a `value` (stored in data) and a `label` (displayed in UI). | | `placeholder` | `string` | Yes | Placeholder text shown in the input field when empty.<br>Only used for display in the admin editor.<br>Applies to `string`, `text`, `url`, `email`, `number`, and `date` types. | | `required` | `boolean` | Yes | Whether a value must be provided when saving the content.<br>Defaults to `false` when not specified.<br>Required fields that are empty will cause a validation error on save. | | `type` | `SchemaFieldType` | No | Field type that determines the editor widget, validation, and serialization.<br>See SchemaFieldType for the complete list and how each type maps<br>to a value in `customData`.<br><br>- `'richtext'` values are automatically converted to HTML strings by the API.<br>- `'image'` and `'file'` values are CDN URLs.<br>- `'object'` and `'array'` types require nested fields definitions.<br>- `'select'` and `'multiselect'` types require options definitions. | | `validation` | `SchemaFieldValidation` | Yes | Validation rules for this field.<br>Applied both in the admin editor (client-side) and on the API (server-side).<br>See SchemaFieldValidation for which rules apply to which field types. | --- ## `SchemaFieldOption` *Interface* Option definition for `select` and `multiselect` field types. Only meaningful when the parent field's `type` is `'select'` or `'multiselect'`. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `label` | `string` | No | Human-readable label displayed in the admin editor's dropdown.<br>Can contain spaces and special characters. | | `value` | `string` | No | Machine-readable value stored in `customData` when this option is selected.<br>Must be unique within the field's options array. | --- ## `SchemaFieldType` *TypeAlias* Supported field types in a content schema. Each type determines how the field is rendered in the admin editor and how its value is serialized in the `customData` object: | Type | Value in customData | | --- | --- | | `string` | Plain text string | | `text` | Multi-line text string | | `richtext` | HTML string (converted from TipTap editor) | | `number` | Numeric value (integer or float) | | `boolean` | `true` or `false` | | `select` | Single string from the options list | | `multiselect` | Array of strings from the options list | | `image` | CDN URL string of the uploaded image | | `file` | CDN URL string of the uploaded file | | `url` | Full URL string (validated) | | `email` | Email address string (validated) | | `date` | ISO 8601 date string (`YYYY-MM-DD`) | | `datetime` | ISO 8601 datetime string | | `color` | Hex color string (e.g. `#ff5500`) | | `object` | Nested object with sub-fields | | `array` | Array of objects with sub-fields | | `dataSource` | Resolved data from a DataSource reference | ```typescript type SchemaFieldType = "string" | "text" | "richtext" | "number" | "boolean" | "select" | "multiselect" | "image" | "file" | "url" | "email" | "date" | "datetime" | "color" | "object" | "array" | "dataSource" ``` --- ## `SchemaFieldValidation` *Interface* Validation rules for a schema field. Which rules are applicable depends on the field's `type`: - `minLength` / `maxLength`: apply to `string`, `text`, `richtext`, `url`, `email` - `min` / `max`: apply to `number` - `pattern`: applies to `string`, `text` Invalid combinations (e.g. `minLength` on a `number` field) are ignored. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `max` | `number` | Yes | Maximum numeric value (inclusive).<br>Only applies to `number` field type. Ignored for non-numeric types. | | `maxLength` | `number` | Yes | Maximum text length (inclusive).<br>Applies to string-like field types (`string`, `text`, `richtext`, `url`, `email`).<br>Ignored for non-string types. | | `min` | `number` | Yes | Minimum numeric value (inclusive).<br>Only applies to `number` field type. Ignored for non-numeric types. | | `minLength` | `number` | Yes | Minimum text length (inclusive).<br>Applies to string-like field types (`string`, `text`, `richtext`, `url`, `email`).<br>Ignored for non-string types. | | `pattern` | `string` | Yes | Regular expression pattern for validation.<br>Only applies to `string` and `text` field types.<br>Must be a valid JavaScript regex pattern (without delimiters or flags). | --- # SDK Types: Content **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Content > **Related services:** **ContentsService** — `lynkow.contents.getBySlug()`, `lynkow.contents.list()` | **TagsService** — `lynkow.tags.list()` ## `Author` *Interface* The author of a content item. Corresponds to the user who created the content in the admin dashboard. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `avatarUrl` | `string \| null` | No | Absolute URL to the author's avatar image, or `null` if no avatar is set.<br>When available, points to a Cloudflare R2-hosted image.<br>Use `ImageVariants.avatar` (128x128) for optimized display if available elsewhere. | | `fullName` | `string` | No | The author's display name (e.g. `'Jane Doe'`).<br>This is the user's full name as set in their account profile. | | `id` | `number` | No | Unique numeric user ID.<br>Stable across all content by the same author. | --- ## `Content` *Interface* Full content item with body, SEO metadata, structured data, and relations. Returned by single-content endpoints (`GET /public/contents/:idOrSlug`). Extends ContentSummary with all the fields omitted from list responses. Extends: `ContentSummary` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `author` | `Author \| null` | No | The content author, or `null` if no author is assigned.<br>Corresponds to the admin user who created the content. | | `body` | `string` | No | The article body as an HTML string, converted server-side from the TipTap editor format.<br>Render with `dangerouslySetInnerHTML` (React) or `v-html` (Vue).<br><br>For structured content (when the content belongs to a `'structured'` category),<br>this may be empty — use `customData` instead. | | `canonicalUrl` | `string \| null` | No | Override canonical URL for this content. Max 1000 characters. Must be a valid URL.<br>`null` if not set — in that case, the canonical URL is the content's own `path`.<br>Use this when the same content is syndicated from another source. | | `categories` | `Category[]` | No | Categories this content belongs to. At least one category is required for published content.<br>The first category in the array is the "primary" category used for URL path generation in nested mode.<br>May contain multiple categories for cross-listing purposes. | | `createdAt` | `string` | No | ISO 8601 datetime when the content was created (e.g. `'2024-01-10T08:00:00.000+00:00'`).<br>Set automatically on initial creation and never changes. | | `customData` | `Record<string, any> \| null` | Yes | Custom field data for structured content.<br>Populated when the content belongs to a category with `contentMode: 'structured'`.<br>The keys and value types are defined by the category's schema.<br>Richtext fields are automatically converted to HTML strings by the API.<br>`null` for standard content (which uses `body` instead). | | `excerpt` | `string \| null` | No | Short summary text for previews and listing cards.<br>Either a manually written excerpt or auto-generated from the first ~160 characters of the body.<br>`null` if the content has no body text and no manual excerpt.<br>Plain text (no HTML). | | `featuredImage` | `string \| null` | No | Absolute URL to the original (full-resolution) featured image.<br>`null` if no featured image is set for this content.<br>Prefer `featuredImageVariants` for optimized, CDN-resized versions. | | `featuredImageVariants` | `ImageVariants` | Yes | CDN-optimized image variants of the featured image at multiple preset sizes.<br>`undefined` when no featured image is set or the site has no image transformations configured.<br><br>Available presets: `thumbnail` (400x300), `card` (600x400), `content` (1200w),<br>`medium` (960w), `hero` (1920w), `og` (1200x630), `full` (2560w).<br><br>Use these instead of `featuredImage` for better performance and responsive layouts. | | `id` | `number` | No | Unique numeric content ID. Auto-incremented.<br>Use this for API calls like `GET /public/contents/:id`. | | `keywords` | `string \| null` | No | Comma-separated SEO keywords (e.g. `'typescript, api, headless cms'`). Max 500 characters.<br>`null` if not set. Used for `<meta name="keywords">` tag.<br>Note: most search engines no longer use this for ranking, but it can be useful for internal search. | | `locale` | `string` | No | BCP 47 locale code of this content (e.g. `'en'`, `'fr'`, `'es'`).<br>Matches one of the site's enabled locales.<br>When fetching content lists, this reflects the `locale` query parameter or the site's default locale. | | `metaDescription` | `string \| null` | No | Custom meta description for SEO. Max 500 characters.<br>`null` if not set — consider using `excerpt` as a fallback for `<meta name="description">`.<br>Recommended length: 150-160 characters for optimal search engine display. | | `metaTitle` | `string \| null` | No | Custom meta title for SEO. Max 255 characters.<br>`null` if not set — in that case, use `title` as a fallback for `<title>` and `og:title`. | | `ogImage` | `string \| null` | No | Absolute URL to the Open Graph image for social sharing.<br>`null` if not set — falls back to the featured image when sharing on social platforms.<br>Max 1000 characters. Must be a valid URL. Recommended size: 1200x630px. | | `ogImageVariants` | `ImageVariants` | Yes | CDN-optimized variants of the Open Graph image at multiple preset sizes.<br>`undefined` when no OG image is set.<br>Use the `og` variant (1200x630) for social sharing meta tags. | | `path` | `string` | No | Full URL path including locale prefix, blog prefix, and category hierarchy.<br>Format depends on site configuration:<br>- Mono-language flat: `'/blog/my-article'`<br>- Multi-language flat: `'/en/blog/my-article'`<br>- Multi-language nested: `'/en/blog/parent-category/child-category/my-article'`<br><br>Use this to build links to the content on your frontend.<br>Does **not** include the domain — prepend your site's base URL. | | `publishedAt` | `string` | No | ISO 8601 datetime when the content was published (e.g. `'2024-03-15T10:30:00.000+00:00'`).<br>Set when the content transitions from `draft` to `published` status.<br>Can also be manually set by editors to backdate or schedule content. | | `slug` | `string` | No | URL-friendly slug, unique within the site for this locale.<br>Lowercase, hyphenated (e.g. `'my-article-title'`). Max 500 characters.<br>Can be used instead of `id` in API calls: `GET /public/contents/:slug`. | | `status` | `"published"` | No | Publication status. Always `'published'` via the public API.<br>Draft, scheduled, and archived content is never returned by public endpoints. | | `structuredData` | `StructuredData \| null` | No | Pre-generated structured data including Article JSON-LD, FAQ JSON-LD, and alternate language links.<br>`null` if structured data generation is disabled for this site or failed.<br><br>Inject each `jsonLd` object as a `<script type="application/ld+json">` tag in `<head>`. | | `tags` | `Tag[]` | No | Tags associated with this content. Can be empty.<br>Tags are flat labels (no hierarchy) used for filtering and grouping content. | | `title` | `string` | No | The content title in the requested locale.<br>Max 500 characters. Always non-empty for published content. | | `updatedAt` | `string` | No | ISO 8601 datetime of the last modification (e.g. `'2024-06-01T14:00:00.000+00:00'`).<br>Updated whenever the content is saved, including metadata-only changes. | --- ## `ContentBody` *TypeAlias* Content body as an HTML string. The API converts the TipTap editor's JSON content tree to HTML server-side, so you receive a ready-to-render HTML string. This includes all formatting, images, tables, code blocks, and FAQ blocks. **Security note:** This HTML is generated server-side from the TipTap editor and is safe to render. No user-submitted raw HTML is passed through. ```typescript type ContentBody = string ``` --- ## `ContentSummary` *Interface* Lightweight content representation returned in paginated list endpoints (`GET /public/contents`). Omits the full body, SEO fields, and relations to minimize payload size. Use `Content` (from `GET /public/contents/:idOrSlug`) for the complete data. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `createdAt` | `string` | No | ISO 8601 datetime when the content was created (e.g. `'2024-01-10T08:00:00.000+00:00'`).<br>Set automatically on initial creation and never changes. | | `excerpt` | `string \| null` | No | Short summary text for previews and listing cards.<br>Either a manually written excerpt or auto-generated from the first ~160 characters of the body.<br>`null` if the content has no body text and no manual excerpt.<br>Plain text (no HTML). | | `featuredImage` | `string \| null` | No | Absolute URL to the original (full-resolution) featured image.<br>`null` if no featured image is set for this content.<br>Prefer `featuredImageVariants` for optimized, CDN-resized versions. | | `featuredImageVariants` | `ImageVariants` | Yes | CDN-optimized image variants of the featured image at multiple preset sizes.<br>`undefined` when no featured image is set or the site has no image transformations configured.<br><br>Available presets: `thumbnail` (400x300), `card` (600x400), `content` (1200w),<br>`medium` (960w), `hero` (1920w), `og` (1200x630), `full` (2560w).<br><br>Use these instead of `featuredImage` for better performance and responsive layouts. | | `id` | `number` | No | Unique numeric content ID. Auto-incremented.<br>Use this for API calls like `GET /public/contents/:id`. | | `locale` | `string` | No | BCP 47 locale code of this content (e.g. `'en'`, `'fr'`, `'es'`).<br>Matches one of the site's enabled locales.<br>When fetching content lists, this reflects the `locale` query parameter or the site's default locale. | | `path` | `string` | No | Full URL path including locale prefix, blog prefix, and category hierarchy.<br>Format depends on site configuration:<br>- Mono-language flat: `'/blog/my-article'`<br>- Multi-language flat: `'/en/blog/my-article'`<br>- Multi-language nested: `'/en/blog/parent-category/child-category/my-article'`<br><br>Use this to build links to the content on your frontend.<br>Does **not** include the domain — prepend your site's base URL. | | `publishedAt` | `string` | No | ISO 8601 datetime when the content was published (e.g. `'2024-03-15T10:30:00.000+00:00'`).<br>Set when the content transitions from `draft` to `published` status.<br>Can also be manually set by editors to backdate or schedule content. | | `slug` | `string` | No | URL-friendly slug, unique within the site for this locale.<br>Lowercase, hyphenated (e.g. `'my-article-title'`). Max 500 characters.<br>Can be used instead of `id` in API calls: `GET /public/contents/:slug`. | | `title` | `string` | No | The content title in the requested locale.<br>Max 500 characters. Always non-empty for published content. | --- ## `Tag` *Interface* A content tag used for cross-category classification and filtering. Tags provide a flat (non-hierarchical) labeling system for content articles. Unlike categories, tags have no parent-child relationships and are primarily used for filtering and discovery. Use `contents.list({ tag: 'slug' })` to fetch all articles with a specific tag. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `id` | `number` | No | Auto-incremented unique identifier.<br>Stable across locales -- the same tag has the same ID in all locale versions. | | `locale` | `string` | No | Locale code for this tag instance (e.g. `'en'`, `'fr'`).<br>Tags are returned in the locale matching the request.<br>The same logical tag may have different `name` and `slug` values in each locale. | | `name` | `string` | No | Display name of the tag (e.g. `'JavaScript'`, `'Getting Started'`).<br>Localized -- the value changes depending on the requested locale. | | `slug` | `string` | No | URL-friendly slug derived from the tag name (e.g. `'javascript'`, `'getting-started'`).<br>Used as the filter value in `contents.list({ tag: slug })`.<br>Unique within a site + locale combination.<br>Localized -- each locale can have a different slug for the same tag. | --- ## `TipTapMark` *Interface* A TipTap inline mark (bold, italic, link, etc.) applied to text nodes. Marks modify the visual presentation or semantics of text within a TipTap node. You typically don't need to process these directly since the public API returns pre-rendered HTML via `Content.body`. These types are provided for advanced use cases like custom rendering pipelines. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `attrs` | `Record<string, unknown>` | Yes | Mark-specific attributes. For example:<br>- `link`: `{ href: 'https://...', target: '_blank' }`<br>- `textStyle`: `{ color: '#ff0000' }`<br><br>`undefined` for simple marks like `bold` or `italic` that have no attributes. | | `type` | `string` | No | The mark type. Common built-in types include `'bold'`, `'italic'`, `'link'`,<br>`'code'`, `'strike'`, `'underline'`. Custom mark types may also appear as plain strings. | --- ## `TipTapNode` *Interface* A TipTap editor node representing a block or inline element in the content tree. The public API returns pre-rendered HTML in `Content.body`, so you typically don't need to process these nodes directly. These types are provided for advanced use cases like: - Building a custom renderer (e.g. for React Native or email templates) - Extracting specific content blocks (e.g. all images, all headings) - Content analysis or transformation pipelines | Property | Type | Optional | Description | | --- | --- | --- | --- | | `attrs` | `Record<string, unknown>` | Yes | Node-specific attributes. Examples:<br>- `heading`: `{ level: 2 }` (1-6)<br>- `image`: `{ src: '...', alt: '...', title: '...' }`<br>- `codeBlock`: `{ language: 'typescript' }`<br>- `tableCell`: `{ colspan: 1, rowspan: 1 }`<br><br>`undefined` for nodes that carry no attributes (e.g. `paragraph`, `listItem`). | | `content` | `TipTapNode[]` | Yes | Ordered child nodes forming the content tree.<br>Block-level nodes (paragraphs, headings, lists) contain other nodes.<br>Leaf nodes like `'text'` and `'horizontalRule'` have no children. | | `marks` | `TipTapMark[]` | Yes | Inline marks applied to this text node (bold, italic, link, etc.).<br>Only meaningful on `'text'` type nodes. Multiple marks can be combined<br>(e.g. bold + italic + link). | | `text` | `string` | Yes | Text content, present **only** on `'text'` type nodes.<br>For all other node types, the textual content is in child `content` nodes. | | `type` | `string` | No | The node type. Determines the semantic meaning and rendering behavior.<br>Common types: `'doc'` (root), `'paragraph'`, `'heading'`, `'text'`, `'image'`,<br>`'bulletList'`, `'orderedList'`, `'listItem'`, `'blockquote'`, `'codeBlock'`,<br>`'horizontalRule'`, `'table'`/`'tableRow'`/`'tableCell'`/`'tableHeader'`,<br>`'faqBlock'`/`'faqItem'`/`'faqQuestion'`/`'faqAnswer'`.<br>Custom node types may also appear as plain strings. | --- # SDK Types: Configuration **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Configuration > **Related services:** **SiteService** — `lynkow.site.getConfig()` ## `Client` *Interface* Extended client interface for SDK v3 Extends: `LynkowClient` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `analytics` | `AnalyticsService` | No | Analytics service (browser-only) | | `availableLocales` | `string[]` | No | Available locales from site configuration | | `blocks` | `BlocksService` | No | Alias for blocks (v3 naming) | | `branding` | `BrandingService` | No | Branding service (badge for free plan) | | `categories` | `CategoriesService` | No | Content categories service.<br>Provides `list()`, `tree()`, `getBySlug()` for fetching categories<br>with content counts and hierarchical tree structures. | | `config` | `Readonly<{ baseUrl: string; debug: boolean; siteId: string }>` | No | Client configuration (readonly) | | `consent` | `ConsentService` | No | Consent service (cookie consent management) | | `contents` | `ContentsService` | No | Blog contents (articles) service.<br>Provides `list()`, `getBySlug()` for fetching published articles<br>with support for filtering by category, tag, search, and pagination. | | `cookies` | `CookiesService` | No | Cookie consent service.<br>Provides `getConfig()` for banner configuration, categories, and third-party<br>scripts, and `logConsent()` to record the user's consent preferences<br>for GDPR compliance. | | `enhancements` | `EnhancementsService` | No | Content enhancements service (code copy, etc.) | | `forms` | `FormsService` | No | Forms service.<br>Provides `getBySlug()` for fetching form schemas and `submit()` for<br>sending form submissions. Supports honeypot and reCAPTCHA spam protection. | | `globals` | `BlocksService` | No | Global blocks service (v3 naming) | | `legal` | `LegalService` | No | Legal documents service.<br>Provides `list()` and `getBySlug()` for fetching legal pages<br>(privacy policy, terms of service, etc.).<br>Legal documents are pages tagged with `'legal'`. | | `locale` | `string` | No | Current locale | | `media` | `MediaHelperService` | No | Image transformation helpers.<br>Use these to build optimized image URLs for responsive images. | | `pages` | `PagesService` | No | Pages (site blocks) service.<br>Provides `list()`, `getBySlug()` for fetching standalone pages<br>with schema-driven data and DataSource resolution.<br>Pages are different from contents -- they represent structural site pages<br>(e.g. homepage, about, contact) rather than blog articles. | | `paths` | `PathsService` | No | Paths service for static site generation (SSG/ISR).<br>Provides `list()` for discovering all renderable paths (contents + categories),<br>`resolve()` for matching a URL path to its content or category,<br>and `getRedirects()` for fetching all redirect rules. | | `reviews` | `ReviewsService` | No | Customer reviews service.<br>Provides `list()` for fetching approved reviews, `submit()` for posting<br>new reviews, and `getSettings()` for the site's review configuration.<br>Only publicly approved reviews are returned. | | `search` | `SearchService` | No | Lynkow Instant Search service | | `seo` | `SeoService` | No | SEO files service.<br>Provides `getRobotsTxt()` for the robots.txt content and `getSitemap()`<br>for the XML sitemap. Used for server-side rendering of SEO routes. | | `site` | `SiteService` | No | Site configuration service.<br>Provides `getConfig()` for fetching site metadata, active locale,<br>and all global blocks in a single request. Also provides `getI18nConfig()`<br>for language switching UI data and `getFullConfig()` for complete site info. | | `tags` | `TagsService` | No | Content tags service.<br>Provides `list()` for fetching all tags.<br>Tags are used to filter contents and group articles across categories. | --- ## `ClientConfig` *Interface* Extended configuration for SDK v3 Extends: `LynkowConfig` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `baseUrl` | `string` | Yes | Base URL of the Lynkow API.<br>Must not include a trailing slash.<br>Override this for self-hosted deployments or local development. | | `debug` | `boolean` | Yes | Enable debug logging | | `fetchOptions` | `RequestInit` | Yes | Default `fetch()` options merged into every request made by this client.<br>Commonly used for Next.js caching directives or custom headers.<br>Per-request `fetchOptions` are shallow-merged on top of these defaults. | | `locale` | `string` | Yes | Default locale code for all requests made by this client.<br>Must be a locale enabled on the site (e.g. `'en'`, `'fr'`, `'de'`).<br>Can be overridden per-request via `BaseRequestOptions.locale`.<br>When omitted, the site's default locale is used for all requests. | | `siteId` | `string` | No | UUID of the Lynkow site to connect to.<br>Found in the admin dashboard under Site Settings.<br>Must be a valid UUID v4 format (lowercase hex with dashes).<br>Sent as the `X-Site-Id` header on every API request. | --- ## `LynkowClient` *Interface* Lynkow Client interface -- the main SDK entry point. Provides access to all public API services through typed service objects. Created via `createLynkow(config)`. Each service corresponds to a public API resource and provides methods for listing, fetching, and (where applicable) submitting data. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `blocks` | `BlocksService` | No | Global blocks service.<br>Provides `getBySlug()` for fetching reusable site-wide data blocks<br>(e.g. header navigation, footer links, banners).<br>Global blocks are resolved from DataSources server-side.<br>Typically fetched once via `site.getConfig()` which returns all globals. | | `categories` | `CategoriesService` | No | Content categories service.<br>Provides `list()`, `tree()`, `getBySlug()` for fetching categories<br>with content counts and hierarchical tree structures. | | `contents` | `ContentsService` | No | Blog contents (articles) service.<br>Provides `list()`, `getBySlug()` for fetching published articles<br>with support for filtering by category, tag, search, and pagination. | | `cookies` | `CookiesService` | No | Cookie consent service.<br>Provides `getConfig()` for banner configuration, categories, and third-party<br>scripts, and `logConsent()` to record the user's consent preferences<br>for GDPR compliance. | | `forms` | `FormsService` | No | Forms service.<br>Provides `getBySlug()` for fetching form schemas and `submit()` for<br>sending form submissions. Supports honeypot and reCAPTCHA spam protection. | | `legal` | `LegalService` | No | Legal documents service.<br>Provides `list()` and `getBySlug()` for fetching legal pages<br>(privacy policy, terms of service, etc.).<br>Legal documents are pages tagged with `'legal'`. | | `pages` | `PagesService` | No | Pages (site blocks) service.<br>Provides `list()`, `getBySlug()` for fetching standalone pages<br>with schema-driven data and DataSource resolution.<br>Pages are different from contents -- they represent structural site pages<br>(e.g. homepage, about, contact) rather than blog articles. | | `paths` | `PathsService` | No | Paths service for static site generation (SSG/ISR).<br>Provides `list()` for discovering all renderable paths (contents + categories),<br>`resolve()` for matching a URL path to its content or category,<br>and `getRedirects()` for fetching all redirect rules. | | `reviews` | `ReviewsService` | No | Customer reviews service.<br>Provides `list()` for fetching approved reviews, `submit()` for posting<br>new reviews, and `getSettings()` for the site's review configuration.<br>Only publicly approved reviews are returned. | | `search` | `SearchService` | No | Lynkow Instant Search service.<br>Provides `search()` for server-side search and `getConfig()` for<br>client-side direct search with tenant tokens. | | `seo` | `SeoService` | No | SEO files service.<br>Provides `getRobotsTxt()` for the robots.txt content and `getSitemap()`<br>for the XML sitemap. Used for server-side rendering of SEO routes. | | `site` | `SiteService` | No | Site configuration service.<br>Provides `getConfig()` for fetching site metadata, active locale,<br>and all global blocks in a single request. Also provides `getI18nConfig()`<br>for language switching UI data and `getFullConfig()` for complete site info. | | `tags` | `TagsService` | No | Content tags service.<br>Provides `list()` for fetching all tags.<br>Tags are used to filter contents and group articles across categories. | --- ## `LynkowConfig` *Interface* Configuration for creating a Lynkow client instance. Only `siteId` is required; all other options have sensible defaults. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `baseUrl` | `string` | Yes | Base URL of the Lynkow API.<br>Must not include a trailing slash.<br>Override this for self-hosted deployments or local development. | | `fetchOptions` | `RequestInit` | Yes | Default `fetch()` options merged into every request made by this client.<br>Commonly used for Next.js caching directives or custom headers.<br>Per-request `fetchOptions` are shallow-merged on top of these defaults. | | `locale` | `string` | Yes | Default locale code for all requests made by this client.<br>Must be a locale enabled on the site (e.g. `'en'`, `'fr'`, `'de'`).<br>Can be overridden per-request via `BaseRequestOptions.locale`.<br>When omitted, the site's default locale is used for all requests. | | `siteId` | `string` | No | UUID of the Lynkow site to connect to.<br>Found in the admin dashboard under Site Settings.<br>Must be a valid UUID v4 format (lowercase hex with dashes).<br>Sent as the `X-Site-Id` header on every API request. | --- ## `SiteConfig` *Interface* Public site configuration returned by `site.getFullConfig()`. Contains all site-level settings needed to bootstrap a frontend application. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `analytics` | `{ consentMode: "opt-in" \| "opt-out" \| "disabled" }` | No | Analytics consent configuration for the site. | | `defaultLocale` | `string` | No | Default locale code for the site (e.g. `'en'`, `'fr'`).<br>Content and API responses default to this locale when no locale is specified.<br>Always appears in enabledLocales. | | `description` | `string \| null` | No | Short site description for SEO and metadata.<br>`null` when no description has been set by the admin. | | `domain` | `string` | No | Primary domain without protocol (e.g. `'example.com'`, `'blog.example.com'`).<br>This is the canonical domain for building absolute URLs. | | `enabledLocales` | `string[]` | No | Array of all enabled locale codes (e.g. `['en', 'fr', 'de']`).<br>Always contains at least defaultLocale.<br>Use this to validate locale parameters or build locale-prefixed URLs. | | `faviconUrl` | `string \| null` | No | Full CDN URL to the site favicon.<br>`null` when no favicon has been uploaded.<br>Should be used in the `<link rel="icon">` tag. | | `i18n` | `I18nConfig` | No | Full i18n configuration including locale display metadata,<br>language switcher settings, and automatic detection behavior.<br>See I18nConfig for details. | | `id` | `string` | No | Site UUID. Same value as the `siteId` used to initialize the client.<br>Format: UUID v4 (e.g. `'550e8400-e29b-41d4-a716-446655440000'`). | | `logoUrl` | `string \| null` | No | Full CDN URL to the site logo image.<br>`null` when no logo has been uploaded.<br>Typically used in the site header and Open Graph metadata. | | `name` | `string` | No | Site display name, as configured by the admin.<br>Used for site branding, document titles, etc. | | `showBranding` | `boolean` | No | Whether the Lynkow branding badge should be displayed on the site.<br>`true` on free plans; `false` on paid plans where branding is removed. | | `timezone` | `string \| null` | No | IANA timezone identifier for the site (e.g. `'Europe/Paris'`, `'America/New_York'`).<br>Used for displaying dates in the site's local time.<br>`null` when no timezone has been configured (defaults to UTC). | --- # SDK Types: Categories **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Categories > **Related services:** **CategoriesService** — `lynkow.categories.getBySlug()`, `lynkow.categories.list()`, `lynkow.categories.tree()` ## `Category` *Interface* A content category as returned in content relations and list select projections. Categories organize content hierarchically and control how content is authored: - **Standard mode**: content uses the TipTap rich text editor (`body` field) - **Structured mode**: content uses custom fields defined by the category's schema (`customData` field) This is the lightweight version — see CategoryWithCount and CategoryDetail for progressively richer representations. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `contentMode` | `"standard" \| "structured"` | Yes | Determines how content in this category is authored:<br>- `'standard'`: articles use the TipTap rich text editor (stored in `Content.body`)<br>- `'structured'`: articles use custom fields defined by the category's schema (stored in `Content.customData`)<br><br>Only present in detail responses (`GET /public/categories/:id`), not in lightweight<br>list projections (e.g. when categories are embedded in content responses). | | `id` | `number` | No | Unique numeric category ID. Auto-incremented.<br>Use this for API calls like `GET /public/categories/:id`. | | `locale` | `string` | No | BCP 47 locale code of this category (e.g. `'en'`, `'fr'`).<br>Categories are locale-specific — each locale has its own set of categories. | | `name` | `string` | No | Category display name in the requested locale (e.g. `'Technology'`, `'Tutorials'`).<br>Max 255 characters. | | `slug` | `string` | No | URL-friendly slug, unique within the site for this locale.<br>Lowercase, hyphenated (e.g. `'web-development'`). Max 255 characters.<br>Used in URL path generation for nested blog URL mode. | --- ## `CategoryDetail` *Interface* Full category detail with parent relationship. Returned by single-category endpoints (`GET /public/categories/:id`). Extends CategoryWithCount with the parent category reference, enabling you to reconstruct the category hierarchy. Extends: `CategoryWithCount` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `contentCount` | `number` | No | Number of **published** content items in this category (for the current locale).<br>Does not count draft, scheduled, or archived content.<br>Does not include content from child categories — each category counts independently. | | `contentMode` | `"standard" \| "structured"` | Yes | Determines how content in this category is authored:<br>- `'standard'`: articles use the TipTap rich text editor (stored in `Content.body`)<br>- `'structured'`: articles use custom fields defined by the category's schema (stored in `Content.customData`)<br><br>Only present in detail responses (`GET /public/categories/:id`), not in lightweight<br>list projections (e.g. when categories are embedded in content responses). | | `description` | `string \| null` | No | Category description as plain text.<br>`null` if no description is set.<br>Useful for SEO meta descriptions on category listing pages. | | `id` | `number` | No | Unique numeric category ID. Auto-incremented.<br>Use this for API calls like `GET /public/categories/:id`. | | `image` | `string \| null` | No | Absolute URL to the category's image (original size).<br>`null` if no image is set.<br>Prefer `imageVariants` for optimized, CDN-resized versions. | | `imageVariants` | `ImageVariants` | Yes | CDN-optimized image variants of the category image at multiple preset sizes.<br>`undefined` when no image is set or the site has no image transformations configured.<br><br>Available presets: `thumbnail` (400x300), `card` (600x400), `content` (1200w),<br>`medium` (960w), `hero` (1920w), `og` (1200x630), `full` (2560w). | | `locale` | `string` | No | BCP 47 locale code of this category (e.g. `'en'`, `'fr'`).<br>Categories are locale-specific — each locale has its own set of categories. | | `name` | `string` | No | Category display name in the requested locale (e.g. `'Technology'`, `'Tutorials'`).<br>Max 255 characters. | | `parentId` | `number \| null` | No | ID of the parent category, or `null` for root-level categories.<br>Use this to build breadcrumbs or reconstruct the category tree.<br>Categories support unlimited nesting depth. | | `path` | `string` | No | Full URL path for this category's listing page.<br>Format depends on site configuration:<br>- Mono-language: `'/blog/parent/child'`<br>- Multi-language: `'/en/blog/parent/child'`<br><br>Includes the blog prefix and full parent category slug chain.<br>Does **not** include the domain — prepend your site's base URL. | | `schema` | `ContentSchema \| null` | Yes | Field schema for structured categories (when `contentMode` is `'structured'`).<br>Defines the custom fields available for content created in this category.<br>`null` or `undefined` when `contentMode` is `'standard'`.<br><br>The schema may be the category's own or inherited from a parent category<br>(check `schemaSource` to distinguish). | | `schemaSource` | `"own" \| "inherit" \| null` | Yes | Where the schema originates from:<br>- `'own'`: this category defines its own field schema<br>- `'inherit'`: the schema is inherited from a parent category (the nearest ancestor with a schema)<br>- `null`: no schema is set (standard content mode)<br><br>When `'inherit'`, the `schema` field contains the parent's schema.<br>Editing the parent's schema will automatically update all inheriting children. | | `slug` | `string` | No | URL-friendly slug, unique within the site for this locale.<br>Lowercase, hyphenated (e.g. `'web-development'`). Max 255 characters.<br>Used in URL path generation for nested blog URL mode. | --- ## `CategoryTreeNode` *Interface* Recursive category tree node, returned by the tree endpoint (`GET /public/categories/tree`). Each node contains its full CategoryWithCount data plus a `children` array of nested sub-categories. The tree is pre-sorted by the API. Extends: `CategoryWithCount` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `children` | `CategoryTreeNode[]` | No | Direct child categories of this node.<br>Empty array (`[]`) for leaf categories with no sub-categories.<br>Recursively contains the full tree structure to unlimited depth. | | `contentCount` | `number` | No | Number of **published** content items in this category (for the current locale).<br>Does not count draft, scheduled, or archived content.<br>Does not include content from child categories — each category counts independently. | | `contentMode` | `"standard" \| "structured"` | Yes | Determines how content in this category is authored:<br>- `'standard'`: articles use the TipTap rich text editor (stored in `Content.body`)<br>- `'structured'`: articles use custom fields defined by the category's schema (stored in `Content.customData`)<br><br>Only present in detail responses (`GET /public/categories/:id`), not in lightweight<br>list projections (e.g. when categories are embedded in content responses). | | `description` | `string \| null` | No | Category description as plain text.<br>`null` if no description is set.<br>Useful for SEO meta descriptions on category listing pages. | | `id` | `number` | No | Unique numeric category ID. Auto-incremented.<br>Use this for API calls like `GET /public/categories/:id`. | | `image` | `string \| null` | No | Absolute URL to the category's image (original size).<br>`null` if no image is set.<br>Prefer `imageVariants` for optimized, CDN-resized versions. | | `imageVariants` | `ImageVariants` | Yes | CDN-optimized image variants of the category image at multiple preset sizes.<br>`undefined` when no image is set or the site has no image transformations configured.<br><br>Available presets: `thumbnail` (400x300), `card` (600x400), `content` (1200w),<br>`medium` (960w), `hero` (1920w), `og` (1200x630), `full` (2560w). | | `locale` | `string` | No | BCP 47 locale code of this category (e.g. `'en'`, `'fr'`).<br>Categories are locale-specific — each locale has its own set of categories. | | `name` | `string` | No | Category display name in the requested locale (e.g. `'Technology'`, `'Tutorials'`).<br>Max 255 characters. | | `path` | `string` | No | Full URL path for this category's listing page.<br>Format depends on site configuration:<br>- Mono-language: `'/blog/parent/child'`<br>- Multi-language: `'/en/blog/parent/child'`<br><br>Includes the blog prefix and full parent category slug chain.<br>Does **not** include the domain — prepend your site's base URL. | | `schema` | `ContentSchema \| null` | Yes | Field schema for structured categories (when `contentMode` is `'structured'`).<br>Defines the custom fields available for content created in this category.<br>`null` or `undefined` when `contentMode` is `'standard'`.<br><br>The schema may be the category's own or inherited from a parent category<br>(check `schemaSource` to distinguish). | | `schemaSource` | `"own" \| "inherit" \| null` | Yes | Where the schema originates from:<br>- `'own'`: this category defines its own field schema<br>- `'inherit'`: the schema is inherited from a parent category (the nearest ancestor with a schema)<br>- `null`: no schema is set (standard content mode)<br><br>When `'inherit'`, the `schema` field contains the parent's schema.<br>Editing the parent's schema will automatically update all inheriting children. | | `slug` | `string` | No | URL-friendly slug, unique within the site for this locale.<br>Lowercase, hyphenated (e.g. `'web-development'`). Max 255 characters.<br>Used in URL path generation for nested blog URL mode. | --- ## `CategoryWithCount` *Interface* Category with URL path, description, image, and content count. Returned by list endpoints (`GET /public/categories`). Extends Category with display-oriented fields useful for rendering category listing pages, navigation menus, and sidebar widgets. Extends: `Category` | Property | Type | Optional | Description | | --- | --- | --- | --- | | `contentCount` | `number` | No | Number of **published** content items in this category (for the current locale).<br>Does not count draft, scheduled, or archived content.<br>Does not include content from child categories — each category counts independently. | | `contentMode` | `"standard" \| "structured"` | Yes | Determines how content in this category is authored:<br>- `'standard'`: articles use the TipTap rich text editor (stored in `Content.body`)<br>- `'structured'`: articles use custom fields defined by the category's schema (stored in `Content.customData`)<br><br>Only present in detail responses (`GET /public/categories/:id`), not in lightweight<br>list projections (e.g. when categories are embedded in content responses). | | `description` | `string \| null` | No | Category description as plain text.<br>`null` if no description is set.<br>Useful for SEO meta descriptions on category listing pages. | | `id` | `number` | No | Unique numeric category ID. Auto-incremented.<br>Use this for API calls like `GET /public/categories/:id`. | | `image` | `string \| null` | No | Absolute URL to the category's image (original size).<br>`null` if no image is set.<br>Prefer `imageVariants` for optimized, CDN-resized versions. | | `imageVariants` | `ImageVariants` | Yes | CDN-optimized image variants of the category image at multiple preset sizes.<br>`undefined` when no image is set or the site has no image transformations configured.<br><br>Available presets: `thumbnail` (400x300), `card` (600x400), `content` (1200w),<br>`medium` (960w), `hero` (1920w), `og` (1200x630), `full` (2560w). | | `locale` | `string` | No | BCP 47 locale code of this category (e.g. `'en'`, `'fr'`).<br>Categories are locale-specific — each locale has its own set of categories. | | `name` | `string` | No | Category display name in the requested locale (e.g. `'Technology'`, `'Tutorials'`).<br>Max 255 characters. | | `path` | `string` | No | Full URL path for this category's listing page.<br>Format depends on site configuration:<br>- Mono-language: `'/blog/parent/child'`<br>- Multi-language: `'/en/blog/parent/child'`<br><br>Includes the blog prefix and full parent category slug chain.<br>Does **not** include the domain — prepend your site's base URL. | | `schema` | `ContentSchema \| null` | Yes | Field schema for structured categories (when `contentMode` is `'structured'`).<br>Defines the custom fields available for content created in this category.<br>`null` or `undefined` when `contentMode` is `'standard'`.<br><br>The schema may be the category's own or inherited from a parent category<br>(check `schemaSource` to distinguish). | | `schemaSource` | `"own" \| "inherit" \| null` | Yes | Where the schema originates from:<br>- `'own'`: this category defines its own field schema<br>- `'inherit'`: the schema is inherited from a parent category (the nearest ancestor with a schema)<br>- `null`: no schema is set (standard content mode)<br><br>When `'inherit'`, the `schema` field contains the parent's schema.<br>Editing the parent's schema will automatically update all inheriting children. | | `slug` | `string` | No | URL-friendly slug, unique within the site for this locale.<br>Lowercase, hyphenated (e.g. `'web-development'`). Max 255 characters.<br>Used in URL path generation for nested blog URL mode. | --- # SDK Types: API Responses **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: API Responses ## `CategoriesListResponse` *Interface* Response from `categories.list()`. Returns all categories (flat list, not paginated) with their content counts. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `blogUrlMode` | `string` | No | How blog content URLs are constructed for this site:<br>- `'flat'`: URLs use slug only, e.g. `/blog/my-article`<br>- `'nested'`: URLs include the category path, e.g. `/blog/tech/my-article`<br><br>This affects how you should build links to content articles.<br>Configured in the site's SEO settings. | | `data` | `CategoryWithCount[]` | No | Flat array of all categories with their published content count.<br>Not paginated -- all categories are returned in a single response. | | `locale` | `string \| null` | No | Active locale code (e.g. `'en'`, `'fr'`) for the returned data, or `null`<br>when the site's default locale was used (no locale override was requested). | --- ## `CategoryDetailResponse` *Interface* Response from `categories.getBySlug()`. Contains the full category details along with its paginated content list. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `category` | `CategoryDetail` | No | Full category details including description, image, content count,<br>schema (for structured categories), and parent info. | | `contents` | `{ data: ContentSummary[]; meta: PaginationMeta }` | No | Paginated list of published content summaries belonging to this category.<br>Supports the same pagination options as `contents.list()`. | | `locale` | `string \| null` | No | Active locale code (e.g. `'en'`, `'fr'`) for the returned data, or `null`<br>when the site's default locale was used (no locale override was requested). | --- ## `CategoryResolveResponse` *Interface* Path resolution response when the resolved path matches a category. Returned by `paths.resolve()` when the given URL corresponds to a category listing. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `blogUrlMode` | `string` | No | Blog URL mode active on this site (`'flat'` or `'nested'`). | | `category` | `CategoryDetail` | No | Full category details including description, image, content count,<br>and parent category reference. | | `contents` | `{ data: ContentSummary[]; meta: PaginationMeta }` | No | Paginated list of published content summaries belonging to the resolved category.<br>Pagination can be controlled via query parameters on the resolve request. | | `locale` | `string` | No | Locale that was resolved for this path (e.g. `'en'`, `'fr'`). | | `type` | `"category"` | No | Discriminator field. Always `'category'` for this response type.<br>Use this to narrow the ResolveResponse union. | --- ## `CategoryTreeResponse` *Interface* Response from `categories.tree()`. Returns categories organized in a parent-child hierarchy. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `blogUrlMode` | `string` | No | How blog content URLs are constructed for this site:<br>- `'flat'`: URLs use slug only, e.g. `/blog/my-article`<br>- `'nested'`: URLs include the category path, e.g. `/blog/tech/my-article`<br><br>Use this to determine whether category slugs should appear in content URLs. | | `data` | `CategoryTreeNode[]` | No | Root-level category nodes, each containing nested `children` arrays.<br>Categories with a `parentId` appear as children of their parent node. | | `locale` | `string \| null` | No | Active locale code (e.g. `'en'`, `'fr'`) for the returned data, or `null`<br>when the site's default locale was used (no locale override was requested). | --- ## `ConsentLogResponse` *Interface* Response from `cookies.logConsent()`. Records the user's cookie consent preferences for GDPR compliance. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `consentId` | `string` | No | Unique UUID identifying this consent record.<br>Can be stored client-side as proof of consent for auditing purposes.<br>Format: UUID v4 (e.g. `'550e8400-e29b-41d4-a716-446655440000'`). | | `message` | `string` | No | Confirmation message (e.g. "Consent recorded"). | --- ## `ContentResolveResponse` *Interface* Path resolution response when the resolved path matches a content article. Returned by `paths.resolve()` when the given URL corresponds to a published article. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `blogUrlMode` | `string` | No | Blog URL mode active on this site (`'flat'` or `'nested'`).<br>Determines the URL structure used to build the resolved path. | | `content` | `Content` | No | Full content object for the matched article, including body, SEO fields,<br>categories, tags, author, and customData. | | `locale` | `string` | No | Locale that was resolved for this path (e.g. `'en'`, `'fr'`).<br>May differ from the requested locale if the content only exists in another locale. | | `type` | `"content"` | No | Discriminator field. Always `'content'` for this response type.<br>Use this to narrow the ResolveResponse union. | --- ## `ContentsListResponse` *Interface* Response from `contents.list()`. Returns content summaries (without body) for performance. Use `contents.getBySlug()` to fetch the full content with body and relations. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `ContentSummary[]` | No | Array of content summaries for the current page.<br>Each summary omits the full `body`, `customData`, and SEO fields.<br>Sorted by `publishedAt` descending by default. | | `meta` | `PaginationMeta` | No | Pagination metadata for navigating through the result set. | --- ## `FormSubmitResponse` *Interface* Response from `forms.submit()`. The `status` field determines whether the submission was immediately accepted or requires further action (e.g. email confirmation for double opt-in). | Property | Type | Optional | Description | | --- | --- | --- | --- | | `message` | `string` | No | Human-readable message suitable for display to the user.<br>Corresponds to the form's configured `successMessage` when status is `'success'`. | | `status` | `"success" \| "pending"` | No | Submission outcome:<br>- `'success'`: submission was accepted immediately and stored.<br>- `'pending'`: form uses double opt-in; a confirmation email was sent to the<br> submitter and the submission will only be stored once they click the<br> confirmation link. | | `submissionId` | `number` | Yes | Auto-incremented submission ID.<br>Only present when `status` is `'success'`.<br>Not available for `'pending'` submissions (ID is assigned after confirmation). | --- ## `GlobalBlockResponse` *Interface* Response from `blocks.getBySlug()`. Returns a single global block with its fully resolved data. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `GlobalBlock` | No | The global block with all DataSource references resolved to actual data.<br>Check `_warnings` for any DataSources that failed to resolve. | --- ## `PagesListResponse` *Interface* Response from `pages.list()`. Returns page summaries (without resolved data) in a single non-paginated response. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `PageSummary[]` | No | Array of page summaries. Does not include the resolved `data` or `seo` fields.<br>Use `pages.getBySlug()` to fetch the full page with resolved data. | --- ## `PaginatedResponse` *Interface* Generic paginated response wrapping any item type. Used internally by the SDK; most endpoints return a concrete typed response instead. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `T[]` | No | Array of items for the current page. Empty array when no results match. | | `meta` | `PaginationMeta` | No | Pagination metadata describing total count, page position, and navigation flags. | --- ## `PaginationMeta` *Interface* Pagination metadata returned with every paginated list response. All numeric values are integers. Page numbering starts at 1. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `currentPage` | `number` | No | 1-based index of the current page.<br>Always between 1 and lastPage inclusive. | | `firstPage` | `number` | No | 1-based index of the first page. Always 1. | | `hasMorePages` | `boolean` | No | `true` when `currentPage < lastPage`, meaning a subsequent page request<br>will return more results. | | `hasPreviousPages` | `boolean` | No | `true` when `currentPage > 1`, meaning a previous page exists. | | `lastPage` | `number` | No | 1-based index of the last page.<br>Computed as `Math.ceil(total / perPage)`. Equals 1 when total is 0. | | `perPage` | `number` | No | Maximum number of items returned per page.<br>Corresponds to the `limit` (or deprecated `perPage`) filter sent in the request.<br>Defaults to 15 when not specified. Maximum allowed value is 100. | | `total` | `number` | No | Total number of items matching the query across all pages.<br>Always >= 0. When 0, data is an empty array. | --- ## `PathsListResponse` *Interface* Response from `paths.list()`. Used for static site generation (SSG/ISR) to discover all renderable paths. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `blogUrlMode` | `string` | No | How blog content URLs are constructed for this site:<br>- `'flat'`: URLs use slug only, e.g. `/blog/my-article`<br>- `'nested'`: URLs include the category path, e.g. `/blog/tech/my-article`<br><br>Determines whether category segments appear in the generated paths. | | `locale` | `string \| null` | No | Active locale code (e.g. `'en'`, `'fr'`), or `null` when the site's<br>default locale was used (no locale override was requested). | | `paths` | `Path[]` | No | Array of all published content and category paths.<br>Includes every locale variant. Use this to generate static pages<br>with Next.js `generateStaticParams()` or similar SSG mechanisms. | --- ## `ResolveResponse` *TypeAlias* Union type for path resolution responses. Use the `type` discriminator field to narrow to the specific response: ```typescript type ResolveResponse = ContentResolveResponse | CategoryResolveResponse ``` --- ## `ReviewsListResponse` *Interface* Response from `reviews.list()`. Only approved reviews are returned through the public API. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `Review[]` | No | Array of approved reviews for the current page.<br>Reviews with status other than `'approved'` are never included. | | `meta` | `PaginationMeta` | No | Pagination metadata for navigating through the result set. | --- ## `ReviewSubmitResponse` *Interface* Response from `reviews.submit()`. The `status` field depends on the site's review moderation settings. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `message` | `string` | No | Human-readable message suitable for display to the user.<br>Content varies based on whether the review was auto-approved or queued for moderation. | | `reviewId` | `number` | Yes | Auto-incremented review ID.<br>Only present when `status` is `'success'` (review was auto-approved).<br>Not available for `'pending'` reviews until they are approved. | | `status` | `"success" \| "pending"` | No | Submission outcome:<br>- `'success'`: the review was auto-approved and is immediately visible on the site.<br> This happens when `requireApproval` is `false` in the review settings.<br>- `'pending'`: the review was queued for moderation and will only appear publicly<br> after a site admin approves it. | --- ## `SiteConfigResponse` *Interface* Response from `site.getConfig()`. Contains basic site metadata and all resolved global blocks. Typically fetched once at app initialization and cached. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `{ globals: Record<string, GlobalBlock>; locale: string; site: { domain: string; favicon: string \| null; logo: string \| null; name: string } }` | No | Top-level data wrapper. | --- ## `TagsListResponse` *Interface* Response from `tags.list()`. Returns all tags in a single non-paginated response. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `data` | `Tag[]` | No | Flat array of all tags for the current locale.<br>Not paginated -- all tags are returned at once. | --- # SDK Types: Analytics **Publié le** : 2026-04-09 **Catégorie** : Types # SDK Types: Analytics > **Related services:** **AnalyticsService** — `lynkow.analytics.destroy()`, `lynkow.analytics.disable()`, `lynkow.analytics.enable()`, `lynkow.analytics.getTracker()`, `lynkow.analytics.init()`, `lynkow.analytics.isEnabled()`, `lynkow.analytics.isInitialized()`, `lynkow.analytics.trackEvent()`, `lynkow.analytics.trackPageview()` ## `EventData` *Interface* Data for tracking custom events | Property | Type | Optional | Description | | --- | --- | --- | --- | | `type` | `string` | No | Event type | --- ## `EventName` *TypeAlias* ```typescript type EventName = keyof LynkowEvents ``` --- ## `LynkowEvents` *Interface* Event types emitted by the SDK. Each key is an event name; the value type is the payload passed to listeners. Use `void` payload events with `emit(event, undefined as any)`. | Property | Type | Optional | Description | | --- | --- | --- | --- | | `consent-changed` | `Record<string, boolean>` | No | Emitted when the user updates their cookie consent preferences.<br>Payload is a map of category ID to consent boolean<br>(e.g. `{ necessary: true, analytics: false, marketing: false }`).<br>Use this to inject or remove third-party scripts based on consent. | | `error` | `Error` | No | Emitted when an SDK operation encounters an unrecoverable error.<br>Payload is the `Error` (typically a LynkowError) that occurred.<br>Use this for global error logging or displaying error notifications. | | `locale-changed` | `string` | No | Emitted when the active locale changes (e.g. via user action or detection).<br>Payload is the new locale code (e.g. `'fr'`, `'en'`).<br>Use this to re-fetch locale-dependent data (contents, categories, pages). | | `ready` | `void` | No | Emitted once when the SDK client is fully initialized and ready to make requests.<br>No payload. Subscribe before calling any service methods to ensure readiness. | --- ## `PageviewData` *Interface* Data for tracking pageviews | Property | Type | Optional | Description | | --- | --- | --- | --- | | `path` | `string` | Yes | Page path (default: window.location.pathname) | | `referrer` | `string` | Yes | Referrer URL | | `title` | `string` | Yes | Page title (default: document.title) | --- # TagsService **Publié le** : 2026-04-09 **Catégorie** : Services # TagsService Service for retrieving content tags. Accessible via `lynkow.tags`. Tags are lightweight labels attached to blog articles for cross-cutting classification (unlike categories which are hierarchical). Responses are cached for 5 minutes (SHORT TTL). Access via: `lynkow.tags` ## Methods **2** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached tag responses. Call this after knowing tags have been updated to force fresh data on the next request. Returns: `void` --- ### `list` ```typescript list(options?: BaseRequestOptions): Promise<TagsListResponse> ``` Retrieves the complete list of tags available on the site. Returns all tags (no pagination). Cached for 5 minutes per locale. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch localized tag names | Returns: `Promise<TagsListResponse>` ```typescript const { data } = await lynkow.tags.list() data.forEach(tag => console.log(tag.name)) // "Featured", "Tutorial", etc. ``` --- # SiteService **Publié le** : 2026-04-09 **Catégorie** : Services # SiteService Service for retrieving the public site configuration. Accessible via `lynkow.site`. Provides site-level metadata such as name, domain, locales, timezone, branding settings, and analytics consent mode. Cached for 10 minutes (MEDIUM TTL) since site configuration rarely changes. Access via: `lynkow.site` ## Methods **2** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates the cached site configuration. Call this if you know the site settings have changed (e.g. after a locale is added) and want to force a fresh fetch on the next `getConfig()` call. Returns: `void` --- ### `getConfig` ```typescript getConfig(): Promise<SiteConfig> ``` Retrieves the complete public site configuration, including site identity (name, domain, logo, favicon), localization settings (default locale, enabled locales, i18n config with detection rules), branding options, and analytics consent mode. Cached for 10 minutes. Returns: `Promise<SiteConfig>` ```typescript const config = await lynkow.site.getConfig() console.log(config.enabledLocales) // ['en', 'fr', 'de'] console.log(config.i18n.detection.enabled) // true console.log(config.analytics.consentMode) // 'opt-in' ``` --- # SeoService **Publié le** : 2026-04-09 **Catégorie** : Services # SeoService Service for retrieving SEO-related files (sitemap, robots.txt, LLM-optimized content, and individual Markdown exports). Accessible via `lynkow.seo`. All methods return plain text (XML, TXT, or Markdown) and are NOT cached by the SDK since they are typically served as route handlers with their own HTTP caching headers. Access via: `lynkow.seo` ## Methods **6** methods ### `getMarkdown` ```typescript getMarkdown(contentPath: string, options?: BaseRequestOptions): Promise<string> ``` Retrieves a single content article or page as Markdown by its public URL path. The SDK appends `.md` to the path automatically. This is useful for exposing individual content items to LLMs or for Markdown-based rendering pipelines. | Parameter | Type | Description | | --- | --- | --- | | `contentPath` | `string` | The content's public URL path (must start with `/` or will be auto-prefixed):<br> - For blog articles: use `Content.path` directly (already includes locale prefix)<br> - For pages in single-language sites: use `Page.path` directly<br> - For pages in multi-language sites: prepend the locale manually,<br> e.g. `/${page.locale}${page.path}` | | `options` | `BaseRequestOptions` | Request options (custom fetch options) | Returns: `Promise<string>` ```typescript // Get a blog article as Markdown (path includes locale automatically) const article = await lynkow.contents.getBySlug('my-article') const md = await lynkow.seo.getMarkdown(article.path) // Get a page as Markdown (mono-language) const page = await lynkow.pages.getBySlug('about') const md = await lynkow.seo.getMarkdown(page.path!) // Get a page as Markdown (multi-language — prepend locale) const page = await lynkow.pages.getBySlug('about') const md = await lynkow.seo.getMarkdown(`/${page.locale}${page.path}`) // In a Next.js catch-all route handler export async function GET(req: Request) { const url = new URL(req.url) const path = url.pathname.replace(/\.md$/, '') const md = await lynkow.seo.getMarkdown(path) return new Response(md, { headers: { 'Content-Type': 'text/markdown; charset=utf-8' } }) } ``` --- ### `llmsFullTxt` ```typescript llmsFullTxt(options?: BaseRequestOptions): Promise<string> ``` Retrieves the llms-full.txt file, which concatenates all published articles and pages into a single Markdown document. Useful for AI/LLM ingestion of the site's full content. Can be large for sites with many articles. Supports locale-specific versions for multilingual sites. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options:<br> - `locale` — fetch the full content for a specific locale (e.g. `'en'`).<br> When set, only content in that locale is included. | Returns: `Promise<string>` ```typescript // In a Next.js route handler (app/llms-full.txt/route.ts) export async function GET() { const md = await lynkow.seo.llmsFullTxt() return new Response(md, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }) } ``` --- ### `llmsTxt` ```typescript llmsTxt(options?: BaseRequestOptions): Promise<string> ``` Retrieves the llms.txt file, an LLM-optimized site index in Markdown format. This file provides a structured overview of the site's content, designed for AI crawlers and language models to understand the site's structure. Supports locale-specific versions for multilingual sites. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options:<br> - `locale` — fetch the index for a specific locale (e.g. `'en'`, `'fr'`).<br> When set, the API returns `/{locale}/llms.txt`. When omitted, returns<br> the default locale version. | Returns: `Promise<string>` ```typescript // In a Next.js route handler (app/llms.txt/route.ts) export async function GET() { const md = await lynkow.seo.llmsTxt() return new Response(md, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } }) } // Fetch the French version const md = await lynkow.seo.llmsTxt({ locale: 'fr' }) ``` --- ### `robots` ```typescript robots(options?: BaseRequestOptions): Promise<string> ``` Retrieves the generated robots.txt file for the site, including crawl directives and sitemap references. Typically served as a route handler with `Content-Type: text/plain`. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options (custom fetch options) | Returns: `Promise<string>` ```typescript // In a Next.js route handler (app/robots.txt/route.ts) export async function GET() { const txt = await lynkow.seo.robots() return new Response(txt, { headers: { 'Content-Type': 'text/plain' } }) } ``` --- ### `sitemap` ```typescript sitemap(options?: BaseRequestOptions): Promise<string> ``` Retrieves the complete XML sitemap for the site. If the site uses Sitemap Index mode, this returns the index file pointing to individual parts. Typically served as a route handler with `Content-Type: application/xml`. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options (custom fetch options) | Returns: `Promise<string>` ```typescript // In a Next.js route handler (app/sitemap.xml/route.ts) export async function GET() { const xml = await lynkow.seo.sitemap() return new Response(xml, { headers: { 'Content-Type': 'application/xml' } }) } ``` --- ### `sitemapPart` ```typescript sitemapPart(part: number, options?: BaseRequestOptions): Promise<string> ``` Retrieves a specific sitemap part when Sitemap Index mode is enabled. Each part contains a subset of URLs, useful for large sites that exceed the 50,000 URL limit per sitemap file. | Parameter | Type | Description | | --- | --- | --- | | `part` | `number` | The 1-indexed part number (e.g. `1`, `2`, `3`) | | `options` | `BaseRequestOptions` | Request options (custom fetch options) | Returns: `Promise<string>` ```typescript // In a Next.js dynamic route handler (app/sitemap-[part].xml/route.ts) export async function GET(req: Request, { params }: { params: { part: string } }) { const xml = await lynkow.seo.sitemapPart(parseInt(params.part)) return new Response(xml, { headers: { 'Content-Type': 'application/xml' } }) } ``` --- # SearchService **Publié le** : 2026-04-09 **Catégorie** : Services # SearchService Lynkow Instant Search service. Accessible via `lynkow.search`. Provides full-text search with typo tolerance across all published content. Results are not cached (search queries are dynamic by nature). Search must be enabled in the admin dashboard (**Settings > SEO > Search**) before it can be used. When disabled, all methods return a 503 error. Access via: `lynkow.search` ## Methods **2** methods ### `getConfig` ```typescript getConfig(options?: BaseRequestOptions): Promise<SearchConfig> ``` Get search configuration for client-side direct search. Returns the search host URL, a short-lived tenant token (JWT, 1-hour expiry), and the index name for your site. Use these values to query the search engine directly from the browser without round-tripping through your server. The response is cached for 10 minutes. Tenant tokens expire after 1 hour — for long-lived pages, call this method periodically to refresh the token. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Base request options (custom fetch options) | Returns: `Promise<SearchConfig>` ```typescript const config = await lynkow.search.getConfig() // { // host: 'https://search.lynkow.com', // apiKey: 'eyJhbGciOi...', // indexName: 'site_abc123_def4_...' // } ``` --- ### `search` ```typescript search(query: string, options?: SearchOptions): Promise<SearchResponse> ``` Search published content with typo tolerance. Queries the search index for articles matching the given query string. Results are ordered by relevance and include highlighted matches in the `_formatted` field. | Parameter | Type | Description | | --- | --- | --- | | `query` | `string` | The search query string. Typos are handled automatically<br> (e.g. `'pagniation'` finds articles about pagination). | | `options` | `SearchOptions` | Optional filters and pagination:<br> - `locale` — filter by locale code (e.g. `'fr'`)<br> - `category` — filter by category slug (e.g. `'guides'`)<br> - `tag` — filter by tag slug (e.g. `'featured'`)<br> - `page` — page number, 1-based (default: `1`)<br> - `limit` — results per page, 1--100 (default: `20`) | Returns: `Promise<SearchResponse>` ```typescript // Basic search const results = await lynkow.search.search('forms') // With filters const results = await lynkow.search.search('formulaire', { locale: 'fr', category: 'guides', page: 1, limit: 5, }) // Access highlighted results results.data.forEach(hit => { console.log(hit._formatted?.title || hit.title) }) ``` --- # ReviewsService **Publié le** : 2026-04-09 **Catégorie** : Services # ReviewsService Service for retrieving and submitting customer reviews. Accessible via `lynkow.reviews`. Only reviews with status `'approved'` are returned by the public API. New submissions go through moderation if `requireApproval` is enabled in the review settings. Anti-spam honeypot fields are injected automatically on submissions. Responses are cached for 5 minutes (SHORT TTL). Access via: `lynkow.reviews` ## Methods **5** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached review responses (lists, individual reviews, and settings). This is called automatically after a successful `submit()`, but can also be called manually if needed. Returns: `void` --- ### `getBySlug` ```typescript getBySlug(slugOrId: string): Promise<Review> ``` Retrieves a single approved review by its slug or numeric ID. Returns the full review including any owner response. Cached for 5 minutes. | Parameter | Type | Description | | --- | --- | --- | | `slugOrId` | `string` | The URL slug (e.g. `'excellent-service'`) or numeric ID<br> (as string, e.g. `'42'`) of the review | Returns: `Promise<Review>` ```typescript const review = await lynkow.reviews.getBySlug('excellent-service') console.log(review.rating) // 5 console.log(review.content) // "Best experience ever!" ``` --- ### `list` ```typescript list(filters?: ReviewsFilters, options?: BaseRequestOptions): Promise<ReviewsListResponse> ``` Retrieves a paginated list of approved customer reviews. Only reviews that have passed moderation are returned. Cached for 5 minutes per unique filter combination. | Parameter | Type | Description | | --- | --- | --- | | `filters` | `ReviewsFilters` | Optional filters to narrow down results:<br> - `page` / `limit` — pagination (defaults to page 1)<br> - `minRating` / `maxRating` — filter by rating range (1-5 scale)<br> - `sort` / `order` — sort field and direction (`'asc'` or `'desc'`) | | `options` | `BaseRequestOptions` | Base request options (locale override, custom fetch options) | Returns: `Promise<ReviewsListResponse>` ```typescript // Fetch top-rated reviews (4+ stars) const { data, meta } = await lynkow.reviews.list({ minRating: 4, limit: 10 }) data.forEach(review => { console.log(`${review.authorName}: ${review.rating}/5`) if (review.response) { console.log(`Owner reply: ${review.response.content}`) } }) ``` --- ### `settings` ```typescript settings(): Promise<ReviewSettings> ``` Retrieves the public review settings for this site, including whether reviews are enabled, moderation rules, rating scale, and field configuration. Use this to dynamically render the review submission form. Cached for 10 minutes. Returns: `Promise<ReviewSettings>` ```typescript const settings = await lynkow.reviews.settings() if (!settings.enabled) { // Reviews are disabled, hide the form return } if (settings.fields.email.required) { // Render email field as required } ``` --- ### `submit` ```typescript submit(data: ReviewSubmitData, options?: SubmitOptions & BaseRequestOptions): Promise<ReviewSubmitResponse> ``` Submits a new customer review. Anti-spam honeypot fields (`_hp`, `_ts`) are injected automatically by the SDK. If reCAPTCHA is enabled on the site, pass the token via `options.recaptchaToken`. After a successful submission, the reviews cache is automatically invalidated. | Parameter | Type | Description | | --- | --- | --- | | `data` | `ReviewSubmitData` | The review data to submit:<br> - `authorName` — reviewer's display name (required)<br> - `authorEmail` — reviewer's email (required if settings demand it)<br> - `rating` — numeric rating (must be within the site's min/max range, typically 1-5)<br> - `title` — optional review title (required if settings demand it)<br> - `content` — the review text (required) | | `options` | `SubmitOptions & BaseRequestOptions` | Optional submission options:<br> - `recaptchaToken` — the reCAPTCHA v3 token if reCAPTCHA is enabled<br> - `fetchOptions` — custom fetch options (e.g. AbortSignal) | Returns: `Promise<ReviewSubmitResponse>` ```typescript const result = await lynkow.reviews.submit({ authorName: 'Alice', rating: 5, content: 'Excellent service!' }) if (result.status === 'pending') { // Show "your review is awaiting moderation" } ``` --- # PathsService **Publié le** : 2026-04-09 **Catégorie** : Services # PathsService Service for URL path resolution and static site generation (SSG). Accessible via `lynkow.paths`. Provides methods to list all available paths for static generation, resolve a URL path to its content or category, and check for configured redirects. Cached for 5 minutes (SHORT TTL). Access via: `lynkow.paths` ## Methods **4** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached path responses (list and resolve lookups). Call this after knowing the site's URL structure has changed (e.g. new content published, slugs updated) to force fresh data. Returns: `void` --- ### `list` ```typescript list(options?: BaseRequestOptions): Promise<PathsListResponse> ``` Retrieves all available URL paths for the site, intended for static site generation (SSG). Returns every published content and category path with segments, type, locale, and last modification date. Cached for 5 minutes per locale. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch paths for a specific locale only | Returns: `Promise<PathsListResponse>` ```typescript // In Next.js generateStaticParams() export async function generateStaticParams() { const { paths } = await lynkow.paths.list() return paths.map(p => ({ slug: p.segments })) } ``` --- ### `matchRedirect` ```typescript matchRedirect(path: string, options?: BaseRequestOptions): Promise<Redirect | null> ``` Checks if a URL path has a configured server-side redirect. Returns the redirect rule if one matches, or `null` if no redirect is configured. This method is NOT cached to ensure redirect changes take effect immediately. A 404 response from the API is treated as "no redirect found" (returns `null`). | Parameter | Type | Description | | --- | --- | --- | | `path` | `string` | The URL path to check for redirects (e.g. `'/old-page'`, `'/legacy/url'`) | | `options` | `BaseRequestOptions` | Request options (custom fetch options) | Returns: `Promise<Redirect | null>` ```typescript // In a Next.js middleware const redirect = await lynkow.paths.matchRedirect(pathname) if (redirect) { return NextResponse.redirect(redirect.target, redirect.statusCode) } // No redirect, continue to normal page rendering ``` --- ### `resolve` ```typescript resolve(path: string, options?: BaseRequestOptions): Promise<ResolveResponse> ``` Resolves a URL path to its corresponding content article or category. Returns a discriminated union: check the `type` field to determine whether the path matched a content (`'content'`) or a category (`'category'`). Cached for 5 minutes per path+locale. | Parameter | Type | Description | | --- | --- | --- | | `path` | `string` | The URL path to resolve (e.g. `'/blog/tech/my-article'`, `'/blog/tech'`) | | `options` | `BaseRequestOptions` | Request options; use `locale` to resolve in a specific locale context | Returns: `Promise<ResolveResponse>` ```typescript const result = await lynkow.paths.resolve('/blog/tech/my-article') if (result.type === 'content') { // Render the full article console.log(result.content.title) } else if (result.type === 'category') { // Render the category listing page console.log(result.category.name) console.log(result.contents.data.length) // Articles in this category } ``` --- # PagesService **Publié le** : 2026-04-09 **Catégorie** : Services # PagesService Service for retrieving published pages (Site Blocks of type "page"). Accessible via `lynkow.pages`. Pages are CMS-managed, schema-driven content blocks (e.g. "About", "Contact", legal pages). Unlike blog articles, pages use DataSource-resolved data instead of a rich text body. Responses are cached for 5 minutes (SHORT TTL) when a cache adapter is configured. Access via: `lynkow.pages` ## Methods **5** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached page responses (lists, slug lookups, path lookups, and JSON-LD data). Call this after knowing pages have been updated to force fresh data on the next request. Returns: `void` --- ### `getByPath` ```typescript getByPath(path: string, options?: BaseRequestOptions): Promise<Page> ``` Retrieves a page by its full URL path, which may include nested segments (e.g. `/services/consulting`). Useful when you have the path from a URL but not the slug. Returns the same full page data as `getBySlug()`. Cached for 5 minutes per path+locale. | Parameter | Type | Description | | --- | --- | --- | | `path` | `string` | The full URL path of the page (e.g. `'/services/consulting'`, `'/about'`).<br> Must start with a forward slash. | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch a specific localized version | Returns: `Promise<Page>` ```typescript // Resolve a page from the current URL const page = await lynkow.pages.getByPath('/services/consulting') console.log(page.name) // "Consulting" ``` --- ### `getBySlug` ```typescript getBySlug(slug: string, options?: BaseRequestOptions): Promise<Page> ``` Retrieves a single page by its slug, including fully resolved DataSource data, SEO settings, and alternate locale versions. Cached for 5 minutes per slug+locale. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The unique URL slug of the page (e.g. `'about'`, `'contact'`, `'privacy-policy'`) | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch a specific localized version | Returns: `Promise<Page>` ```typescript const page = await lynkow.pages.getBySlug('about') console.log(page.data) // Resolved DataSource content console.log(page.seo) // SEO metadata console.log(page.alternates) // Other locale versions ``` --- ### `getJsonLd` ```typescript getJsonLd(slug: string, options?: BaseRequestOptions): Promise<Record<string, unknown>> ``` Retrieves Schema.org JSON-LD structured data for a page, ready to be embedded in a `<script type="application/ld+json">` tag for search engine optimization. Cached for 5 minutes per slug+locale. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The page slug to generate JSON-LD for (e.g. `'about'`, `'contact'`) | | `options` | `BaseRequestOptions` | Request options; use `locale` to get locale-specific structured data | Returns: `Promise<Record<string, unknown>>` ```typescript const jsonLd = await lynkow.pages.getJsonLd('about') // Embed in your HTML <head> // <script type="application/ld+json">{JSON.stringify(jsonLd)}</script> ``` --- ### `list` ```typescript list(options?: PagesListOptions): Promise<PagesListResponse> ``` Retrieves a list of all published pages on the site. Optionally filter by tag to get a subset (e.g. legal documents). Returns page summaries without resolved data. Cached for 5 minutes per locale+tag. | Parameter | Type | Description | | --- | --- | --- | | `options` | `PagesListOptions` | Request options:<br> - `tag` — filter pages by tag slug (e.g. `'legal'` for privacy policy, terms, etc.)<br> - `locale` — override the client's default locale | Returns: `Promise<PagesListResponse>` ```typescript // List all pages const { data } = await lynkow.pages.list() // List only legal documents const { data } = await lynkow.pages.list({ tag: 'legal' }) ``` --- # MediaHelperService **Publié le** : 2026-04-09 **Catégorie** : Services # MediaHelperService Service for building optimized image URLs using Cloudflare Image Transformations. Accessible via `lynkow.media`. This is a pure utility service (no API calls, no caching) that constructs Cloudflare `/cdn-cgi/image/` URLs from Lynkow media URLs. Works on both server and browser. Handles both original URLs (`/sites/...`) and already-transformed URLs (`/cdn-cgi/image/...`), re-extracting the original path when needed. Access via: `lynkow.mediaHelper` ## Methods **2** methods ### `srcset` ```typescript srcset(imageUrl: string | null | undefined, options: SrcsetOptions): string ``` Generates an HTML `srcset` attribute value from a Lynkow image URL, suitable for use in an `<img srcset="...">` or `<source srcset="...">` tag. Produces one Cloudflare Image Transformation URL per width breakpoint. | Parameter | Type | Description | | --- | --- | --- | | `imageUrl` | `string \| null \| undefined` | Original image URL from the Lynkow API (e.g. `content.featuredImage`).<br> Accepts `null` or `undefined` safely (returns empty string). | | `options` | `SrcsetOptions` | Configuration for the srcset:<br> - `widths` — array of pixel widths to generate (default: `[400, 800, 1200, 1920]`)<br> - `fit` — resize mode: `'cover'`, `'contain'`, `'scale-down'`, or `'crop'` (default: `'scale-down'`)<br> - `quality` — image quality 1-100 (default: `80`)<br> - `gravity` — focal point for crop mode (e.g. `'0.5x0.3'`) | Returns: `string` ```typescript // Build a responsive <img> tag with multiple widths const srcsetValue = lynkow.media.srcset(article.featuredImage, { widths: [480, 768, 1024, 1440], fit: 'cover', quality: 85, }) const imgTag = `<img srcset="${srcsetValue}" sizes="(max-width: 768px) 100vw, 50vw" alt="${article.title}" />` ``` --- ### `transform` ```typescript transform(imageUrl: string | null | undefined, options: TransformOptions): string ``` Generates a single Cloudflare Image Transformation URL from a Lynkow image URL. Useful for thumbnails, hero images, or any context where you need a specific size/format. | Parameter | Type | Description | | --- | --- | --- | | `imageUrl` | `string \| null \| undefined` | Original image URL from the Lynkow API (e.g. `content.featuredImage`).<br> Accepts `null` or `undefined` safely (returns empty string). | | `options` | `TransformOptions` | Transformation options:<br> - `w` / `h` — target width/height in pixels<br> - `fit` — resize mode: `'cover'`, `'contain'`, `'scale-down'`, or `'crop'` (default: `'scale-down'`)<br> - `quality` — image quality 1-100 (default: `80`)<br> - `format` — output format: `'auto'`, `'webp'`, `'avif'`, or `'jpeg'` (default: `'auto'`)<br> - `gravity` — focal point for crop mode (e.g. `'0.5x0.3'`)<br> - `dpr` — device pixel ratio 1-4 for retina displays | Returns: `string` ```typescript // Build an optimized thumbnail URL for a product card const thumbUrl = lynkow.media.transform(product.image, { w: 400, h: 300, fit: 'cover', format: 'webp', quality: 75, }) document.querySelector('.product-thumb').src = thumbUrl ``` --- # LegalService **Publié le** : 2026-04-09 **Catégorie** : Services # LegalService Service for accessing legal documents (privacy policy, terms of service, etc.). Accessible via `lynkow.legal`. Legal documents are regular pages tagged with `'legal'`. This service provides convenience methods to access them, but is deprecated in favor of the `pages` service. Responses are cached for 5 minutes (SHORT TTL). Access via: `lynkow.legal` ## Methods **3** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached legal document responses. Returns: `void` --- ### `getBySlug` ```typescript getBySlug(slug: string, options?: BaseRequestOptions): Promise<LegalDocument> ``` Retrieves a single legal page by its slug, including resolved data and SEO settings. Cached for 5 minutes per slug+locale. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The page slug (e.g. `'privacy-policy'`, `'terms-of-service'`, `'cookie-policy'`) | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch a specific localized version | Returns: `Promise<LegalDocument>` ```typescript // Deprecated -- migrate to pages service: // Before: const privacy = await lynkow.legal.getBySlug('privacy-policy') // After: const privacy = await lynkow.pages.getBySlug('privacy-policy') console.log(privacy.data.name, privacy.data.body) ``` --- ### `list` ```typescript list(options?: BaseRequestOptions): Promise<LegalDocument[]> ``` Retrieves all published pages tagged with `'legal'`. Returns page summaries including slug, name, path, and locale. Cached for 5 minutes per locale. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch localized versions | Returns: `Promise<LegalDocument[]>` ```typescript // Deprecated -- migrate to pages service: // Before: const docs = await lynkow.legal.list() // After: const docs = await lynkow.pages.list({ tag: 'legal' }) docs.data.forEach(doc => console.log(doc.slug, doc.name)) ``` --- # FormsService **Publié le** : 2026-04-09 **Catégorie** : Services # FormsService Service for retrieving form schemas and submitting form data. Accessible via `lynkow.forms`. Forms are dynamic, CMS-managed forms with configurable fields, validation rules, and spam protection. The service automatically handles honeypot anti-spam fields on submissions. Form schemas are cached for 10 minutes (MEDIUM TTL). Access via: `lynkow.forms` ## Methods **3** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached form schema responses. Call this if you know a form definition has changed and want to fetch the latest schema. Returns: `void` --- ### `getBySlug` ```typescript getBySlug(slug: string): Promise<Form> ``` Retrieves a form definition by its slug, including the field schema, validation rules, settings (submit label, success message), and spam protection configuration (honeypot/reCAPTCHA). Cached for 10 minutes. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The unique slug of the form (e.g. `'contact'`, `'newsletter'`, `'feedback'`) | Returns: `Promise<Form>` ```typescript const form = await lynkow.forms.getBySlug('contact') // Iterate fields to render the form dynamically form.schema.forEach(field => { console.log(field.name, field.type, field.required) }) // Check spam protection config if (form.recaptchaEnabled) { // Render reCAPTCHA widget using form.recaptchaSiteKey } ``` --- ### `submit` ```typescript submit(slug: string, data: FormSubmitData, options?: SubmitOptions & BaseRequestOptions): Promise<FormSubmitResponse> ``` Submits form data to the API. Anti-spam honeypot fields (`_hp`, `_ts`) are injected automatically by the SDK -- you do not need to add them yourself. If the form has reCAPTCHA enabled, pass the token via `options.recaptchaToken`. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The slug of the form to submit to (e.g. `'contact'`) | | `data` | `FormSubmitData` | Key-value pairs matching the form's field names.<br> Values can be `string`, `number`, `boolean`, or `File`. | | `options` | `SubmitOptions & BaseRequestOptions` | Optional submission options:<br> - `recaptchaToken` — the reCAPTCHA v3 token (required if reCAPTCHA is enabled on the form)<br> - `fetchOptions` — custom fetch options (e.g. AbortSignal) | Returns: `Promise<FormSubmitResponse>` ```typescript const result = await lynkow.forms.submit('contact', { name: 'John Doe', email: 'john@example.com', message: 'Hello!' }) if (result.status === 'pending') { // Show "check your email" confirmation } else { // Show success message console.log(result.message) } ``` --- # EnhancementsService **Publié le** : 2026-04-09 **Catégorie** : Services # EnhancementsService Service for adding interactive features to content rendered from the Lynkow API. Accessible via `lynkow.enhancements`. This is a **browser-only** service -- all methods are no-ops on the server. Currently provides: - **Copy button** for code blocks (elements with `[data-copy-code]` attribute) - **Script activation** for inline scripts injected via `dangerouslySetInnerHTML` - **Widget iframe auto-resize** for embedded Lynkow widgets - **CSS normalization** to ensure content renders correctly with CSS frameworks (Tailwind, etc.) A `MutationObserver` automatically detects new content added to the DOM and applies enhancements, making it compatible with SPA frameworks like React/Next.js. Script clones are appended to `<head>` (not inline) to avoid React DOM reconciliation issues. Access via: `lynkow.enhancements` ## Methods **3** methods ### `destroy` ```typescript destroy(): void ``` Cleans up all enhancement resources: disconnects the MutationObserver, removes the widget resize listener, cancels any pending animation frame, removes injected styles and cloned scripts from `<head>`, and resets the initialized state. After calling `destroy()`, you can re-initialize by calling `init()` again. No-op on server. Returns: `void` --- ### `init` ```typescript init(): void ``` Initializes content enhancements: injects CSS styles, binds copy-to-clipboard handlers on code blocks, activates inline scripts, starts the widget iframe resize listener, and sets up a `MutationObserver` to automatically enhance newly added DOM content. Idempotent -- calling multiple times has no effect after the first initialization. No-op on server. Call this manually if you need to re-initialize after the client is created (e.g. after dynamically loading content outside the initial render). Returns: `void` ```typescript // Reinitialize enhancements after dynamically loading new content const html = await fetchArticleContent() document.getElementById('article').innerHTML = html lynkow.enhancements.init() ``` --- ### `isInitialized` ```typescript isInitialized(): boolean ``` Checks whether the enhancements service has been initialized. Returns: `boolean` --- # CookiesService **Publié le** : 2026-04-09 **Catégorie** : Services # CookiesService Low-level service for cookie consent API interactions. Accessible via `lynkow.cookies`. Provides raw API methods for fetching consent configuration and logging user preferences. For a higher-level experience with built-in banner UI and preferences modal, use the `consent` service (`lynkow.consent`) instead. Responses are cached for 10 minutes (MEDIUM TTL). Access via: `lynkow.cookies` ## Methods **3** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates the cached cookie consent configuration. Call this if you know the consent settings have changed and want to force a fresh fetch on the next `getConfig()` call. Returns: `void` --- ### `getConfig` ```typescript getConfig(): Promise<CookieConfig> ``` Retrieves the cookie consent banner configuration from the API, including categories (necessary, analytics, marketing, preferences), banner texts, theming options, and third-party scripts to inject per category. Cached for 10 minutes. Returns: `Promise<CookieConfig>` ```typescript const config = await lynkow.cookies.getConfig() if (config.enabled) { console.log(config.categories) // [{id: 'necessary', ...}, {id: 'analytics', ...}] console.log(config.texts.acceptAll) // "Accept all" } ``` --- ### `logConsent` ```typescript logConsent(preferences: CookiePreferences, options?: BaseRequestOptions): Promise<ConsentLogResponse> ``` Records the user's cookie consent preferences to the server for audit trail / GDPR compliance. This is a write operation (POST) and is never cached. | Parameter | Type | Description | | --- | --- | --- | | `preferences` | `CookiePreferences` | A record mapping category IDs to boolean acceptance values<br> (e.g. `{ necessary: true, analytics: true, marketing: false }`) | | `options` | `BaseRequestOptions` | Base request options (custom fetch options) | Returns: `Promise<ConsentLogResponse>` ```typescript const result = await lynkow.cookies.logConsent({ necessary: true, analytics: true, marketing: false }) console.log(result.consentId) // Unique consent record ID ``` --- # ContentsService **Publié le** : 2026-04-09 **Catégorie** : Services # ContentsService Service for retrieving published blog articles (contents). Accessible via `lynkow.contents`. All methods return only published content visible to the public API. Responses are cached in-memory for 5 minutes (SHORT TTL) when a cache adapter is configured on the client. Access via: `lynkow.contents` ## Methods **3** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached content responses (lists and single articles). Call this after knowing content has been updated to force fresh data on the next request. Returns: `void` --- ### `getBySlug` ```typescript getBySlug(slug: string, options?: BaseRequestOptions): Promise<Content> ``` Retrieves a single published article by its URL-friendly slug. Returns the full content including body (HTML), SEO metadata, author, categories, tags, and structured data (JSON-LD). Cached for 5 minutes per slug+locale combination. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The unique URL slug of the content (e.g. `'my-article'`, `'getting-started-with-typescript'`) | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch a specific localized version | Returns: `Promise<Content>` ```typescript const content = await lynkow.contents.getBySlug('my-article') console.log(content.title) // Article title console.log(content.body) // HTML body string console.log(content.author?.fullName) // Author name console.log(content.categories) // Associated categories ``` --- ### `list` ```typescript list(filters?: ContentsFilters, options?: BaseRequestOptions): Promise<ContentsListResponse> ``` Retrieves a paginated list of published blog articles, sorted by publication date (newest first by default). Results are cached for 5 minutes per unique filter combination. | Parameter | Type | Description | | --- | --- | --- | | `filters` | `ContentsFilters` | Optional filters to narrow down results:<br> - `page` / `limit` — pagination (defaults to page 1, 15 items)<br> - `category` — filter by category slug (e.g. `'tech'`, `'news'`)<br> - `tag` — filter by tag slug (e.g. `'featured'`)<br> - `search` — full-text search across title and body<br> - `sort` / `order` — sort field and direction (`'asc'` or `'desc'`)<br> - `locale` — override the client's default locale | | `options` | `BaseRequestOptions` | Base request options (locale override, custom fetch options) | Returns: `Promise<ContentsListResponse>` ```typescript // Fetch the first page of articles in the "tech" category const { data, meta } = await lynkow.contents.list({ page: 1, limit: 10, category: 'tech' }) // Search for articles const results = await lynkow.contents.list({ search: 'typescript' }) ``` --- # ConsentService **Publié le** : 2026-04-09 **Catégorie** : Services # ConsentService High-level consent management service with built-in banner UI and preferences modal. Accessible via `lynkow.consent`. This is a hybrid service: - **API methods** (`getConfig`, `logConsent`) work everywhere (server + browser) - **UI methods** (`show`, `hide`, `acceptAll`, `rejectAll`, `showPreferences`, etc.) only work in the browser and are no-ops on the server Consent choices are persisted in `localStorage` under the `_lkw_consent` key (compatible with tracker.js) and expire after 365 days. When consent is granted for a category, any third-party scripts configured for that category are automatically injected into the page. Emits a `'consent-changed'` event (via the SDK event emitter) and a `'lynkow:consent:update'` CustomEvent on `document` whenever consent changes. Access via: `lynkow.consent` ## Methods **12** methods ### `acceptAll` ```typescript acceptAll(): void ``` Accepts all cookie categories (necessary, analytics, marketing, preferences), saves the choice to `localStorage`, logs consent to the server, injects all accepted third-party scripts, and hides the banner. No-op on server. Returns: `void` ```typescript // Wire up a one-click "Accept All" button in your own UI document.getElementById('accept-cookies').addEventListener('click', () => { lynkow.consent.acceptAll() }) ``` --- ### `destroy` ```typescript destroy(): void ``` Cleans up all consent UI resources: removes the banner and preferences modal from the DOM, removes injected third-party scripts, and stops the theme observer. Call this when unmounting the SDK (e.g. in a React useEffect cleanup). No-op on server. Returns: `void` --- ### `getCategories` ```typescript getCategories(): ConsentCategories ``` Returns the user's current consent categories. If no consent has been stored yet, returns default values (only `necessary: true`, all others `false`). On the server, always returns defaults. Returns: `ConsentCategories` ```typescript // Read current preferences to conditionally load a third-party widget const prefs = lynkow.consent.getCategories() if (prefs.marketing) { loadChatWidget() } ``` --- ### `getConfig` ```typescript getConfig(): Promise<CookieConfig> ``` Fetches the cookie consent configuration from the API. The result is cached in memory for the lifetime of the service instance (not TTL-based). Works on both server and browser. Returns: `Promise<CookieConfig>` --- ### `hasConsented` ```typescript hasConsented(): boolean ``` Checks whether the user has already made a consent choice (accepted, rejected, or customized). Returns `false` if no consent is stored or if the stored consent has expired (after 365 days). Returns: `boolean` ```typescript // Only show the consent banner if the user hasn't already decided if (!lynkow.consent.hasConsented()) { lynkow.consent.show() } ``` --- ### `hide` ```typescript hide(): void ``` Hides and removes the consent banner from the DOM. Does not affect stored consent preferences. No-op on server or if the banner is not shown. Returns: `void` --- ### `logConsent` ```typescript logConsent(preferences: CookiePreferences, action?: "accept_all" | "reject_all" | "customize" | "withdraw"): Promise<void> ``` Logs the user's consent preferences to the server for GDPR audit trail. Automatically generates or retrieves a persistent visitor ID from `localStorage`. Errors are silently swallowed to avoid disrupting the user experience. Works on both server and browser. | Parameter | Type | Description | | --- | --- | --- | | `preferences` | `CookiePreferences` | Consent preferences as a record of category IDs to booleans<br> (e.g. `{ necessary: true, analytics: true, marketing: false }`) | | `action` | `"accept_all" \| "reject_all" \| "customize" \| "withdraw"` | Optional explicit action type. If omitted, the action is inferred<br> from the preferences (all true = `'accept_all'`, all false = `'reject_all'`,<br> mixed = `'customize'`) | Returns: `Promise<void>` --- ### `rejectAll` ```typescript rejectAll(): void ``` Rejects all optional cookie categories (analytics, marketing, preferences). Only `necessary` remains true (cannot be disabled). Saves the choice to `localStorage`, logs consent to the server, and hides the banner. No third-party scripts are injected. No-op on server. Returns: `void` ```typescript // Reject all non-essential cookies from a custom banner document.getElementById('reject-cookies').addEventListener('click', () => { lynkow.consent.rejectAll() }) ``` --- ### `reset` ```typescript reset(): void ``` Resets all consent choices by clearing `localStorage`, removing any previously injected third-party scripts, and re-showing the consent banner. Useful for providing a "manage cookies" link that lets users change their preferences. No-op on server. Returns: `void` ```typescript // Add a "Manage cookies" link in the footer to let users re-choose document.getElementById('manage-cookies').addEventListener('click', () => { lynkow.consent.reset() }) ``` --- ### `setCategories` ```typescript setCategories(categories: Partial<ConsentCategories>): void ``` Sets specific consent categories, merging with the current state. The `necessary` category is always forced to `true` regardless of input. Saves the choice, logs to server, and injects scripts for accepted categories. No-op on server. | Parameter | Type | Description | | --- | --- | --- | | `categories` | `Partial<ConsentCategories>` | Partial consent categories to update. Unspecified categories<br> retain their current value. Example: `{ analytics: true, marketing: false }` | Returns: `void` ```typescript // Save custom preferences from a preferences form lynkow.consent.setCategories({ analytics: true, marketing: false, preferences: true, }) ``` --- ### `show` ```typescript show(): void ``` Shows the consent banner in the browser. If the user has already consented (choices stored in `localStorage`), the banner is skipped and accepted third-party scripts are injected directly instead. Respects the site's theme (light/dark/auto) and position settings. No-op on server. When theme is `'auto'`, a MutationObserver watches for site theme changes and updates the banner colors in real-time. Returns: `void` ```typescript // Show the consent banner on page load (skips if already consented) const lynkow = createClient({ siteId: '...' }) lynkow.consent.show() ``` --- ### `showPreferences` ```typescript showPreferences(): void ``` Opens the preferences modal, allowing the user to toggle individual consent categories (analytics, marketing, preferences). The necessary category is always checked and disabled. Pre-populates checkboxes with the user's current consent state. No-op on server. Returns: `void` --- # CategoriesService **Publié le** : 2026-04-09 **Catégorie** : Services # CategoriesService Service for retrieving content categories and their hierarchical structure. Accessible via `lynkow.categories`. Categories organize blog articles and can be nested (parent/child). Each category includes a content count and optional image. Responses are cached for 5 minutes (SHORT TTL) when a cache adapter is configured. Access via: `lynkow.categories` ## Methods **4** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached category responses (lists, tree, and detail views). Call this after knowing categories have been updated to force fresh data on the next request. Returns: `void` --- ### `getBySlug` ```typescript getBySlug(slug: string, options?: CategoryOptions & BaseRequestOptions): Promise<CategoryDetailResponse> ``` Retrieves a single category by its slug, along with a paginated list of the published articles that belong to it. Cached for 5 minutes per slug+locale+pagination combination. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The unique URL slug of the category (e.g. `'tech'`, `'news'`, `'tutorials'`) | | `options` | `CategoryOptions & BaseRequestOptions` | Combined category and request options:<br> - `page` / `limit` — pagination for the category's articles (defaults to page 1)<br> - `locale` — override the client's default locale | Returns: `Promise<CategoryDetailResponse>` ```typescript const { category, contents } = await lynkow.categories.getBySlug('tech', { page: 1, limit: 10 }) console.log(category.name) // "Tech" console.log(contents.data.length) // Number of articles on this page console.log(contents.meta.hasMorePages) // Whether more pages exist ``` --- ### `list` ```typescript list(options?: BaseRequestOptions): Promise<CategoriesListResponse> ``` Retrieves a flat list of all categories with their content counts. Useful for building navigation menus, sidebars, or category filters. Cached for 5 minutes per locale. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch localized category names | Returns: `Promise<CategoriesListResponse>` ```typescript const { data, blogUrlMode } = await lynkow.categories.list() data.forEach(cat => { console.log(`${cat.name} (${cat.contentCount} articles)`) }) ``` --- ### `tree` ```typescript tree(options?: BaseRequestOptions): Promise<CategoryTreeResponse> ``` Retrieves the full category hierarchy as a nested tree structure. Root categories appear at the top level, each with a `children` array containing their subcategories (recursively). Useful for building hierarchical navigation or breadcrumbs. Cached for 5 minutes per locale. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch localized category names | Returns: `Promise<CategoryTreeResponse>` ```typescript const { data } = await lynkow.categories.tree() // Iterate root categories and their children data.forEach(root => { console.log(root.name) root.children.forEach(child => { console.log(` - ${child.name}`) }) }) ``` --- # BrandingService **Publié le** : 2026-04-09 **Catégorie** : Services # BrandingService Service for displaying the "Powered by Lynkow" branding badge. Accessible via `lynkow.branding`. This is a **browser-only** service -- all methods are no-ops on the server. The badge HTML and CSS are fetched from the API (cached for 30 minutes, LONG TTL) so they can be updated server-side without SDK changes. The badge automatically adapts to the site's light/dark theme via a MutationObserver. The badge is shown for sites on the free plan (`siteConfig.showBranding === true`). It fails silently if the fetch fails (the badge is not critical). Access via: `lynkow.branding` ## Methods **4** methods ### `destroy` ```typescript destroy(): void ``` Alias for `remove()`. Cleans up the badge, styles, and theme observer. Use this in cleanup callbacks (e.g. React useEffect cleanup). Returns: `void` --- ### `inject` ```typescript inject(): Promise<void> ``` Fetches the badge HTML/CSS from the API and injects it into the page DOM. The badge is appended to `document.body` and styled according to the site's current theme (light/dark). A theme observer is set up to update the badge if the site theme changes dynamically. Idempotent -- calling multiple times has no effect if the badge is already injected. Fails silently on fetch errors. No-op on server. Returns: `Promise<void>` ```typescript // Inject the powered-by badge after the page has loaded const lynkow = createClient({ siteId: '...' }) await lynkow.branding.inject() ``` --- ### `isVisible` ```typescript isVisible(): boolean ``` Checks whether the branding badge is currently present in the DOM. Returns: `boolean` --- ### `remove` ```typescript remove(): void ``` Removes the branding badge and its associated styles from the DOM, and stops the theme observer. No-op on server or if the badge is not currently injected. Returns: `void` --- # BlocksService **Publié le** : 2026-04-09 **Catégorie** : Services # BlocksService Service for retrieving global site blocks (header, footer, navigation, etc.). Accessible via `lynkow.globals` (aliased from `lynkow.blocks`). Global blocks are reusable, schema-driven content components shared across all pages of the site. They are resolved server-side with DataSources. Responses are cached for 10 minutes (MEDIUM TTL) since global blocks change infrequently. Access via: `lynkow.blocks` ## Methods **4** methods ### `clearCache` ```typescript clearCache(): void ``` Invalidates all cached global block responses (site config and individual blocks). Call this after knowing global blocks have been updated to force fresh data on the next request. Returns: `void` --- ### `getBySlug` ```typescript getBySlug(slug: string, options?: BaseRequestOptions): Promise<GlobalBlockResponse> ``` Retrieves a single global block by its slug, with fully resolved DataSource data. Use this when you only need one specific block rather than all globals. Cached for 10 minutes per slug+locale. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The unique slug of the global block (e.g. `'header'`, `'footer'`, `'nav'`, `'sidebar'`) | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch a localized version | Returns: `Promise<GlobalBlockResponse>` ```typescript const { data } = await lynkow.globals.getBySlug('header') console.log(data.name) // "Header" console.log(data.data) // Resolved content (links, logo, menu items, etc.) ``` --- ### `global` ```typescript global(slug: string, options?: BaseRequestOptions): Promise<GlobalBlockResponse> ``` Retrieves a single global block by slug. | Parameter | Type | Description | | --- | --- | --- | | `slug` | `string` | The block slug | | `options` | `BaseRequestOptions` | Request options | Returns: `Promise<GlobalBlockResponse>` ```typescript // Deprecated -- use getBySlug() instead: const { data } = await lynkow.globals.getBySlug('footer') ``` --- ### `siteConfig` ```typescript siteConfig(options?: BaseRequestOptions): Promise<SiteConfigResponse> ``` Retrieves the complete site configuration including basic site info (name, domain, logo, favicon) and all global blocks resolved in a single request. This is the recommended way to fetch layout data for your site shell (header, footer, navigation). Cached for 10 minutes per locale. | Parameter | Type | Description | | --- | --- | --- | | `options` | `BaseRequestOptions` | Request options; use `locale` to fetch localized block data | Returns: `Promise<SiteConfigResponse>` ```typescript const { data } = await lynkow.globals.siteConfig() console.log(data.site.name) // "My Site" const header = data.globals['header'] // Resolved header block data const footer = data.globals['footer'] // Resolved footer block data ``` --- # AnalyticsService **Publié le** : 2026-04-09 **Catégorie** : Services # AnalyticsService Service for client-side analytics tracking via the Lynkow tracker.js script. Accessible via `lynkow.analytics`. This is a **browser-only** service -- all methods are no-ops when called on the server (SSR/Node.js). The service lazily loads the `tracker.js` script from the API, which auto-initializes using the site ID. The tracker automatically captures pageviews; use `trackPageview()` for manual SPA navigation tracking and `trackEvent()` for custom events. The service respects the site's consent mode: if consent mode is `'opt-in'`, tracking only starts after consent is granted. The consent state is read from `localStorage` (`_lkw_consent_mode` key). Access via: `lynkow.analytics` ## Methods **9** methods ### `destroy` ```typescript destroy(): void ``` Removes the tracker.js script element from the DOM and resets the service state. After calling `destroy()`, you can re-initialize by calling `init()` again. No-op on server. Returns: `void` ```typescript // Clean up analytics when unmounting (e.g. React useEffect cleanup) useEffect(() => { lynkow.analytics.init() return () => lynkow.analytics.destroy() }, []) ``` --- ### `disable` ```typescript disable(): void ``` Disables analytics tracking. While disabled, all `trackEvent()` and `trackPageview()` calls become no-ops. The tracker script remains loaded; call `destroy()` to fully remove it. Returns: `void` ```typescript // Disable tracking when the user revokes analytics consent lynkow.analytics.disable() ``` --- ### `enable` ```typescript enable(): void ``` Enables analytics tracking. Tracking is enabled by default when the service is created. Call this to re-enable after a previous `disable()` call. Returns: `void` ```typescript // Re-enable tracking after the user grants analytics consent lynkow.on('consent-changed', (categories) => { if (categories.analytics) { lynkow.analytics.enable() } else { lynkow.analytics.disable() } }) ``` --- ### `getTracker` ```typescript getTracker(): LynkowAnalyticsGlobal | undefined ``` Returns the underlying `window.LynkowAnalytics` global object for advanced usage when you need direct access to the tracker API beyond what this service wraps. Returns: `LynkowAnalyticsGlobal | undefined` --- ### `init` ```typescript init(): Promise<void> ``` Initializes analytics tracking by loading the tracker.js script into the page and waiting for it to auto-initialize. This is idempotent -- calling it multiple times has no effect after the first successful initialization. No-op on server. The tracker script is loaded from `{baseUrl}/analytics/tracker.js` with `data-site-id` and `data-api-url` attributes. If a consent mode is stored in `localStorage`, it is passed via `data-consent-mode`. Returns: `Promise<void>` ```typescript // Manually initialize analytics in an SPA after client-side routing is ready const lynkow = createClient({ siteId: '...' }) await lynkow.analytics.init() ``` --- ### `isEnabled` ```typescript isEnabled(): boolean ``` Returns whether analytics tracking is currently enabled. Returns: `boolean` --- ### `isInitialized` ```typescript isInitialized(): boolean ``` Returns whether the tracker.js script has been loaded and the `window.LynkowAnalytics` global is available. Returns: `boolean` --- ### `trackEvent` ```typescript trackEvent(event: EventData): Promise<void> ``` Tracks a custom event via the Lynkow analytics tracker. Automatically initializes the tracker if not already loaded. No-op on server or when tracking is disabled. | Parameter | Type | Description | | --- | --- | --- | | `event` | `EventData` | Event data object. Must include a `type` string identifier<br> (e.g. `'purchase'`, `'signup'`, `'click'`). Additional properties are<br> passed through as custom event data (any key-value pairs). | Returns: `Promise<void>` ```typescript await lynkow.analytics.trackEvent({ type: 'purchase', productId: 'abc123', amount: 99.99 }) ``` --- ### `trackPageview` ```typescript trackPageview(data?: PageviewData): Promise<void> ``` Manually tracks a pageview event. The tracker automatically captures pageviews on initial page load, so this method is only needed for SPA client-side navigation (e.g. Next.js App Router route changes). Automatically initializes the tracker if not already loaded. No-op on server or when tracking is disabled. | Parameter | Type | Description | | --- | --- | --- | | `data` | `PageviewData` | Optional pageview data:<br> - `path` — page path (defaults to `window.location.pathname`)<br> - `title` — page title (defaults to `document.title`)<br> - `referrer` — referrer URL (defaults to `document.referrer`) | Returns: `Promise<void>` ```typescript // After SPA client-side navigation await lynkow.analytics.trackPageview({ path: '/new-page' }) // Or let it auto-detect from the current URL await lynkow.analytics.trackPageview() ``` --- # Getting Started **Publié le** : 2026-04-09 **Catégorie** : Services # SDK Functions ## `browserOnly` ```typescript function browserOnly(fn: () => T, fallback: T): T ``` Execute a function only in browser environment Returns the fallback value in server environment | Parameter | Type | Description | | --- | --- | --- | | `fn` | `() => T` | Function to execute in browser | | `fallback` | `T` | Value to return in server environment | Returns: `T` ```typescript const visitorId = browserOnly( () => localStorage.getItem('visitor_id'), null ) ``` --- ## `browserOnlyAsync` ```typescript function browserOnlyAsync(fn: () => Promise<T>, fallback: T): Promise<T> ``` Execute an async function only in browser environment Returns the fallback value in server environment | Parameter | Type | Description | | --- | --- | --- | | `fn` | `() => Promise<T>` | Async function to execute in browser | | `fallback` | `T` | Value to return in server environment | Returns: `Promise<T>` ```typescript const config = await browserOnlyAsync( () => fetchConfig(), defaultConfig ) ``` --- ## `createClient` ```typescript function createClient(config: ClientConfig): Client ``` Creates a Lynkow client instance (SDK v3) | Parameter | Type | Description | | --- | --- | --- | | `config` | `ClientConfig` | Client configuration | Returns: `Client` ```typescript const lynkow = createClient({ siteId: 'your-site-uuid', locale: 'fr', debug: true }) const posts = await lynkow.contents.list() ``` --- ## `createLynkowClient` ```typescript function createLynkowClient(config: LynkowConfig): LynkowClient ``` Creates a Lynkow client instance (legacy naming) | Parameter | Type | Description | | --- | --- | --- | | `config` | `LynkowConfig` | Client configuration | Returns: `LynkowClient` ```typescript // Deprecated -- use createClient() instead: const lynkow = createClient({ siteId: 'your-site-uuid', locale: 'fr' }) const posts = await lynkow.contents.list() ``` --- ## `detectSiteTheme` ```typescript function detectSiteTheme(): "light" | "dark" ``` Detect the website's theme by inspecting DOM indicators. Detection cascade: 1. data-theme / data-mode / data-color-scheme attributes on <html> or <body> 2. "dark" class on <html> or <body> (Tailwind convention) 3. CSS color-scheme property on <html> 4. Background color luminance of <body> 5. Fallback: OS-level prefers-color-scheme media query Returns: `"light" | "dark"` ```typescript // Apply a conditional class based on the site's current theme const theme = detectSiteTheme() document.body.classList.add(theme === 'dark' ? 'inverted-text' : 'default-text') ``` --- ## `isCategoryResolve` ```typescript function isCategoryResolve(response: ResolveResponse): response ``` Checks if a resolution response is a category | Parameter | Type | Description | | --- | --- | --- | | `response` | `ResolveResponse` | | Returns: `response` ```typescript // Resolve a URL path and render the appropriate template const resolved = await lynkow.paths.resolve('/blog/tutorials') if (isCategoryResolve(resolved)) { renderCategoryPage(resolved.data) // TypeScript narrows to CategoryResolveResponse } ``` --- ## `isContentResolve` ```typescript function isContentResolve(response: ResolveResponse): response ``` Checks if a resolution response is a content | Parameter | Type | Description | | --- | --- | --- | | `response` | `ResolveResponse` | | Returns: `response` ```typescript // Resolve a URL path and render the appropriate template const resolved = await lynkow.paths.resolve('/blog/my-article') if (isContentResolve(resolved)) { renderArticle(resolved.data) // TypeScript narrows to ContentResolveResponse } ``` --- ## `isLynkowError` ```typescript function isLynkowError(error: unknown): error ``` Type guard to check if an unknown value is a `LynkowError`. Use this in `catch` blocks to safely access `LynkowError` properties. | Parameter | Type | Description | | --- | --- | --- | | `error` | `unknown` | The caught value to check | Returns: `error` ```typescript try { await lynkow.contents.getBySlug('not-found') } catch (error) { if (isLynkowError(error) && error.code === 'NOT_FOUND') { // Handle 404 -- error.status is 404 } } ``` --- ## `onSiteThemeChange` ```typescript function onSiteThemeChange(callback: (theme: "light" | "dark") => void): () => void ``` Observe site theme changes in real-time. Watches for: - Attribute changes on <html> and <body> (data-theme, data-mode, class, style) - OS-level prefers-color-scheme media query changes | Parameter | Type | Description | | --- | --- | --- | | `callback` | `(theme: "light" \| "dark") => void` | Called with the new theme when a change is detected | Returns: `() => void` ```typescript // Update a widget's appearance when the site theme changes const stopObserving = onSiteThemeChange((theme) => { widget.setTheme(theme) }) // Stop observing when no longer needed stopObserving() ``` --- # SDK Reference **Publié le** : 2026-04-09 **Catégorie** : SDK Reference # Lynkow SDK Reference Version: `3.8.68` ## Installation ```bash npm install @lynkow/sdk ``` ## Quick Start ```typescript import { createClient } from '@lynkow/sdk' const lynkow = createClient({ siteId: 'your-site-id', apiUrl: 'https://api.lynkow.com' }) const posts = await lynkow.contents.list() ``` ## Available Services - **AnalyticsService** — Service for client-side analytics tracking via the Lynkow tracker.js script. Accessible via `lynkow.analytics`. This is a **browser-only** service -- all methods are no-ops when called on the server (SSR/Node.js). The service lazily loads the `tracker.js` script from the API, which auto-initializes using the site ID. The tracker automatically captures pageviews; use `trackPageview()` for manual SPA navigation tracking and `trackEvent()` for custom events. The service respects the site's consent mode: if consent mode is `'opt-in'`, tracking only starts after consent is granted. The consent state is read from `localStorage` (`_lkw_consent_mode` key). - **BlocksService** — Service for retrieving global site blocks (header, footer, navigation, etc.). Accessible via `lynkow.globals` (aliased from `lynkow.blocks`). Global blocks are reusable, schema-driven content components shared across all pages of the site. They are resolved server-side with DataSources. Responses are cached for 10 minutes (MEDIUM TTL) since global blocks change infrequently. - **BrandingService** — Service for displaying the "Powered by Lynkow" branding badge. Accessible via `lynkow.branding`. This is a **browser-only** service -- all methods are no-ops on the server. The badge HTML and CSS are fetched from the API (cached for 30 minutes, LONG TTL) so they can be updated server-side without SDK changes. The badge automatically adapts to the site's light/dark theme via a MutationObserver. The badge is shown for sites on the free plan (`siteConfig.showBranding === true`). It fails silently if the fetch fails (the badge is not critical). - **CategoriesService** — Service for retrieving content categories and their hierarchical structure. Accessible via `lynkow.categories`. Categories organize blog articles and can be nested (parent/child). Each category includes a content count and optional image. Responses are cached for 5 minutes (SHORT TTL) when a cache adapter is configured. - **ConsentService** — High-level consent management service with built-in banner UI and preferences modal. Accessible via `lynkow.consent`. This is a hybrid service: - **API methods** (`getConfig`, `logConsent`) work everywhere (server + browser) - **UI methods** (`show`, `hide`, `acceptAll`, `rejectAll`, `showPreferences`, etc.) only work in the browser and are no-ops on the server Consent choices are persisted in `localStorage` under the `_lkw_consent` key (compatible with tracker.js) and expire after 365 days. When consent is granted for a category, any third-party scripts configured for that category are automatically injected into the page. Emits a `'consent-changed'` event (via the SDK event emitter) and a `'lynkow:consent:update'` CustomEvent on `document` whenever consent changes. - **ContentsService** — Service for retrieving published blog articles (contents). Accessible via `lynkow.contents`. All methods return only published content visible to the public API. Responses are cached in-memory for 5 minutes (SHORT TTL) when a cache adapter is configured on the client. - **CookiesService** — Low-level service for cookie consent API interactions. Accessible via `lynkow.cookies`. Provides raw API methods for fetching consent configuration and logging user preferences. For a higher-level experience with built-in banner UI and preferences modal, use the `consent` service (`lynkow.consent`) instead. Responses are cached for 10 minutes (MEDIUM TTL). - **EnhancementsService** — Service for adding interactive features to content rendered from the Lynkow API. Accessible via `lynkow.enhancements`. This is a **browser-only** service -- all methods are no-ops on the server. Currently provides: - **Copy button** for code blocks (elements with `[data-copy-code]` attribute) - **Script activation** for inline scripts injected via `dangerouslySetInnerHTML` - **Widget iframe auto-resize** for embedded Lynkow widgets - **CSS normalization** to ensure content renders correctly with CSS frameworks (Tailwind, etc.) A `MutationObserver` automatically detects new content added to the DOM and applies enhancements, making it compatible with SPA frameworks like React/Next.js. Script clones are appended to `<head>` (not inline) to avoid React DOM reconciliation issues. - **FormsService** — Service for retrieving form schemas and submitting form data. Accessible via `lynkow.forms`. Forms are dynamic, CMS-managed forms with configurable fields, validation rules, and spam protection. The service automatically handles honeypot anti-spam fields on submissions. Form schemas are cached for 10 minutes (MEDIUM TTL). - **LegalService** — Service for accessing legal documents (privacy policy, terms of service, etc.). Accessible via `lynkow.legal`. Legal documents are regular pages tagged with `'legal'`. This service provides convenience methods to access them, but is deprecated in favor of the `pages` service. Responses are cached for 5 minutes (SHORT TTL). - **MediaHelperService** — Service for building optimized image URLs using Cloudflare Image Transformations. Accessible via `lynkow.media`. This is a pure utility service (no API calls, no caching) that constructs Cloudflare `/cdn-cgi/image/` URLs from Lynkow media URLs. Works on both server and browser. Handles both original URLs (`/sites/...`) and already-transformed URLs (`/cdn-cgi/image/...`), re-extracting the original path when needed. - **PagesService** — Service for retrieving published pages (Site Blocks of type "page"). Accessible via `lynkow.pages`. Pages are CMS-managed, schema-driven content blocks (e.g. "About", "Contact", legal pages). Unlike blog articles, pages use DataSource-resolved data instead of a rich text body. Responses are cached for 5 minutes (SHORT TTL) when a cache adapter is configured. - **PathsService** — Service for URL path resolution and static site generation (SSG). Accessible via `lynkow.paths`. Provides methods to list all available paths for static generation, resolve a URL path to its content or category, and check for configured redirects. Cached for 5 minutes (SHORT TTL). - **ReviewsService** — Service for retrieving and submitting customer reviews. Accessible via `lynkow.reviews`. Only reviews with status `'approved'` are returned by the public API. New submissions go through moderation if `requireApproval` is enabled in the review settings. Anti-spam honeypot fields are injected automatically on submissions. Responses are cached for 5 minutes (SHORT TTL). - **SearchService** — Lynkow Instant Search service. Accessible via `lynkow.search`. Provides full-text search with typo tolerance across all published content. Results are not cached (search queries are dynamic by nature). Search must be enabled in the admin dashboard (**Settings > SEO > Search**) before it can be used. When disabled, all methods return a 503 error. - **SeoService** — Service for retrieving SEO-related files (sitemap, robots.txt, LLM-optimized content, and individual Markdown exports). Accessible via `lynkow.seo`. All methods return plain text (XML, TXT, or Markdown) and are NOT cached by the SDK since they are typically served as route handlers with their own HTTP caching headers. - **SiteService** — Service for retrieving the public site configuration. Accessible via `lynkow.site`. Provides site-level metadata such as name, domain, locales, timezone, branding settings, and analytics consent mode. Cached for 10 minutes (MEDIUM TTL) since site configuration rarely changes. - **TagsService** — Service for retrieving content tags. Accessible via `lynkow.tags`. Tags are lightweight labels attached to blog articles for cross-cutting classification (unlike categories which are hierarchical). Responses are cached for 5 minutes (SHORT TTL).