> ## Documentation Index
> Fetch the complete documentation index at: https://docs.makershq.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# React SDK reference

> Use the release reader, website update notifications, and hooks exported by @makershq/react.

To open an exact release, pass its complete app version to the reader. For installation and a full-history example, see [Embed the React reader](https://docs.makershq.dev/integrations/release-notes/react.md).

```tsx theme={null}
import { ReleaseNotes } from '@makershq/react'
import '@makershq/react/styles.css'

export function InstalledRelease() {
  return (
    <ReleaseNotes
      projectId="YOUR_PROJECT_ID"
      version="1.2.3+desktop.7"
      theme="dark"
    />
  )
}
```

## Props

| Prop | Type | Behavior |
| - | - | - |
| `projectId` | `string` (required) | Public Maker’s HQ project ID. |
| `version` | `string` (optional) | Initial exact complete semantic version; omit for full history. |
| `theme` | `system`, `light` or `dark` (optional) | Defaults to `system`; the fallback palette follows the OS live. |

`onReleaseRendered(release, { cached })` runs after the selected document renders. `openExternal(url)` lets your app handle external HTTP(S) links. Both are optional.

## History and exact versions

Without `version`, the reader loads ten notes at a time in publication order, including unversioned notes. With `version`, it looks up that exact release, including any build suffix. If unavailable, visitors can choose **All versions** to return to history.

## Theme and layout

The precompiled stylesheet is scoped to `.fl-release-notes`. It does not require your application to install Tailwind or import private Maker’s HQ CSS.

Existing application variables `--background`, `--foreground`, `--muted-foreground`, `--border`, `--accent` and `--ring` take precedence over the fallback palette selected by `theme`. Supply your own app theme through those variables; setting `theme="dark"` does not override a light value you already define.

Place the reader in a container with the width you want. It adapts to narrow layouts and scrolls long Markdown without requiring an app-wide layout change.

If your app defines a CSP, allow `https://makershq.dev` in `connect-src`. In `img-src`, allow the API origin and the actual redirected storage/CDN or external Markdown image origins. An API-origin-only image policy can block the final signed-image redirect.

## Loading, errors and saved copies

The reader owns loading, empty history, missing exact release, unavailable project, and retry controls. An unavailable exact version offers full history instead of substituting another version. A missing/deleted project is an authoritative unavailable response and clears affected cached content.

A network failure may show a clearly labelled saved copy. The best-effort browser cache is isolated by project and exact version, bounded to five recent views and one megabyte total. It stores Markdown, not image bytes, and tolerates denied storage. A missing exact release never shows another version's cached note. An open saved-copy or failed view retries on the browser's `online` event without polling.

A saved copy is not evidence that a note is still published. When testing, distinguish a live response from the reader's offline label; images may be unavailable while offline.

## WebsiteUpdates props

| Prop | Type | Default |
| - | - | - |
| `projectId` | `string` | Required public project ID |
| `currentBuild` | `{ buildId: string; releaseVersion?: string }` | Required compiled build |
| `manifestUrl` | `string` | `/version.json` |
| `theme` | `system`, `light` or `dark` | `system` |
| `beforeRefresh` | `() => void` or `() => Promise<void>` | No save barrier |

Mount one notification owner per app. The manifest describes the deployed build. The compiled `currentBuild` describes the page already running. Preferences are scoped to project and manifest URL. **Not now** defers the current candidate in this tab. A failed save leaves the page open. [Set up website notifications](https://docs.makershq.dev/integrations/release-notes/website-updates.md).

## Custom UI

Use `useReleaseNotesReader` when your app owns the layout. The hook handles fetches, pagination, retries, and offline recovery. Change the `version` option to switch between an exact note and full history.

```tsx theme={null}
import { useState } from 'react'
import { ReleaseMarkdown, useReleaseNotesReader } from '@makershq/react'

export function CustomJournal({ projectId }: { projectId: string }) {
  const [version, setVersion] = useState<string | undefined>('1.2.3')
  const notes = useReleaseNotesReader({ projectId, version })
  return (
    <section>
      {version && (
        <button onClick={() => setVersion(undefined)}>All releases</button>
      )}
      {notes.loading && <p role="status">Loading…</p>}
      {notes.cached && <p role="status">Saved copy</p>}
      {notes.error && !notes.cached && (
        <p role="alert">
          {notes.unavailable ? 'Notes unavailable.' : 'Could not load notes.'}
        </p>
      )}
      {notes.error && !notes.unavailable && (
        <button onClick={notes.retry}>Try again</button>
      )}
      {notes.page?.items.map((release) => (
        <article key={release.id}>
          <h2>{release.version ?? release.title}</h2>
          <ReleaseMarkdown projectId={projectId} release={release} />
        </article>
      ))}
      {!notes.cached && !notes.error && notes.page?.nextCursor && (
        <button
          disabled={notes.loadingMore}
          onClick={() => void notes.loadMore()}
        >
          More
        </button>
      )}
    </section>
  )
}
```

`ReleaseMarkdown` accepts `projectId`, `release` and optional `openExternal(url)`. It retains restricted HTML, HTTP(S) links and release-scoped images. `releaseImageUrl(projectId, releaseId, source)` returns a safe external/managed URL or `undefined` for a rejected source when your app supplies its own renderer. `ReleaseMarkdownProps` and `ReleaseNotesReaderOptions` are exported beside the existing public data types. Fully custom layout and styling belong to your app; the default stylesheet remains optional for those custom surfaces.

### Custom website notifications

Use **one** `useWebsiteUpdates({ projectId, currentBuild, manifestUrl?, beforeRefresh? })` owner instead of mounting `WebsiteUpdates` alongside another owner. The hook returns `candidate`, `displayedRelease`, `refreshing`, `error` and no-argument `refresh()`, `dismissCandidate()`, `dismissRelease()` and `onReleaseRendered()` callbacks. Keep the loaded build compiled into the bundle, preserve the save barrier, and show a loaded-release preview before an available deployment prompt.

```tsx theme={null}
import { useEffect } from 'react'
import { useWebsiteUpdates, type WebsiteBuild } from '@makershq/react'

export function CustomNotifications({
  projectId,
  currentBuild,
}: {
  projectId: string
  currentBuild: WebsiteBuild
}) {
  const updates = useWebsiteUpdates({ projectId, currentBuild })
  useEffect(() => {
    if (updates.displayedRelease) updates.onReleaseRendered()
  }, [updates.displayedRelease, updates.onReleaseRendered])
  if (updates.displayedRelease)
    return (
      <aside aria-label="What changed">
        <h2>{updates.displayedRelease.title}</h2>
        <a
          href={updates.displayedRelease.url}
          target="_blank"
          rel="noopener noreferrer"
          onClick={updates.dismissRelease}
        >
          Read release
        </a>
        <button onClick={updates.dismissRelease}>Dismiss</button>
      </aside>
    )
  if (!updates.candidate) return null
  return (
    <aside aria-label="New deployment">
      {updates.error && <p role="alert">{updates.error}</p>}
      <button
        disabled={updates.refreshing}
        onClick={() => void updates.refresh()}
      >
        Refresh
      </button>
      <button disabled={updates.refreshing} onClick={updates.dismissCandidate}>
        Later
      </button>
    </aside>
  )
}
```

Call `onReleaseRendered()` after the displayed preview commits. Wire your app’s save function to `beforeRefresh` if visitors can have unsaved work.

## Verify your integration

Open the history and a known exact version, then try an unavailable version and **All versions**. Test a narrow layout, both themes, keyboard navigation, and a disconnect/reconnect. A saved copy is labelled as such until a fresh response arrives.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.