Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion frontlit/docs/_sections.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"version": 1,
"sections": []
"sections": [
{ "folder": "getting-started", "name": "Getting Started", "order": 10 },
{ "folder": "teams", "name": "Teams", "order": 20 },
{ "folder": "self-hosting", "name": "Self hosting", "order": 30 },
{ "folder": "components", "name": "Components", "order": 40 },
{ "folder": "developers", "name": "Developers", "order": 50 }
]
}
137 changes: 137 additions & 0 deletions frontlit/docs/components/page-builder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
title: Page builder
description: Install and use the block-based WYSIWYG page builder and renderer.
nav_order: 10
---

`@frontlit/page-builder` is a block-based page builder and renderer for React. It is self-contained — no backend
and no framework lock-in. Themes, blocks, and the WYSIWYG builder all run client-side.

A page is a `Theme` plus a `WidgetInstance[]` layout. Each widget is one block from the registry (hero, header,
footer, pricing, faq, grid, rich-text, email-form, and more).

Rich text editing and rendering live in [`@frontlit/text-editor`](/docs/components/text-editor), which this package
depends on and installs automatically.

To try the builder, install the package and render `PageBuilder` as shown in [Usage](#usage), or run the [components demo](https://github.com/codelitdev/frontlit/tree/main/examples/components-demo) from the FrontLit repository.

## Installation

```bash
npm i @frontlit/page-builder
# or
pnpm add @frontlit/page-builder
```

Peer dependencies: `react` and `react-dom` (^18 or ^19). Next.js is **not** required.

### CSS

Import the compiled stylesheet. It includes the utilities, default tokens, and the theme-picker fonts:

```ts
import "@frontlit/page-builder/styles.css";
```

If the host already runs Tailwind CSS v4, you can skip `styles.css` and scan the packages instead. Import the
prose sheet so rich-text lists and code blocks survive Tailwind preflight:

```css
@import "@frontlit/page-builder/theme.css";
@import "@frontlit/text-editor/prose.css";
@source "./node_modules/@frontlit/page-builder";
@source "./node_modules/@frontlit/text-editor";
```

## Usage

Both `PageRenderer` and `PageBuilder` are client components (the package ships with a `"use client"` directive on
UI entries).

```tsx
import {
PageRenderer,
PageBuilder,
createDefaultLayout,
getDefaultTheme,
} from "@frontlit/page-builder";
import "@frontlit/page-builder/styles.css";

// Read-only rendering (a published page, a marketing site, a preview):
<PageRenderer
layout={createDefaultLayout()}
theme={getDefaultTheme()}
pageData={{ pageType: "landing" }}
/>;

// The interactive editor — persist however you like:
<PageBuilder
onChange={({ layout, theme }) => {
/* save */
}}
/>;
```

`PageBuilder` holds all editor state in memory and calls `onChange` on every edit. The host app decides how (and
whether) to persist it.

```tsx
import { PageBuilder, type PageBuilderState } from "@frontlit/page-builder";

function Editor({ savedPage }: { savedPage: SavedPage }) {
function handleChange(state: PageBuilderState) {
// Persist state.layout / state.theme / state.seo, e.g. via
// `updatePage(pageId, { layout: state.layout, ... })`.
}

return (
<PageBuilder
initialLayout={savedPage.layout}
initialTheme={savedPage.theme}
initialSeo={savedPage.seo}
pageData={{ pageType: "landing" }}
onChange={handleChange}
/>
);
}
```

`header` and `footer` are required layout-level blocks. The builder repairs legacy or empty initial layouts by
adding them, keeps them at the outer edges, and never permits moving or deleting them.

### Client-side routing

Internal `/...` links render as native `<a>` tags by default, so the package works in Vite, Remix, Astro, or any
other React host. Next.js (or any other router) can inject its own link:

```tsx
import Link from "next/link";
import {
PageRenderer,
type PageBuilderLinkProps,
} from "@frontlit/page-builder";

function AppLink(props: PageBuilderLinkProps) {
return <Link {...props} />;
}

<PageRenderer layout={layout} theme={theme} linkComponent={AppLink} />;
```

`PageBuilder` accepts the same `linkComponent` prop and forwards it to the live preview.

## What's inside

| Subpath | What it exports |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `@frontlit/page-builder` | Everything below, re-exported from one place |
| `@frontlit/page-builder/models` | `Theme`, `WidgetInstance`, `Widget<T>`, `Page`, `Media`, etc. (re-exports `TextEditorContent` from `@frontlit/text-editor`) |
| `@frontlit/page-builder/primitives` | Themed UI atoms (`Button`, `Header1-4`, `Section`, `PageCard`, ...) + built-in themes |
| `@frontlit/page-builder/components` | Shared presentational bits: `Image`, `NavLink`, `VideoWithPreview` (re-exports `TextRenderer` from `@frontlit/text-editor`) |
| `@frontlit/page-builder/blocks` | The block library: Header, Hero, Content, Featured, Faq, Pricing, Banner, EmailForm, Footer, … |
| `@frontlit/page-builder/renderer` | `PageRenderer` — renders a page's `WidgetInstance[]` layout, with an `editing` mode for use inside the builder |
| `@frontlit/page-builder/builder` | `PageBuilder` — the full WYSIWYG editor (add/remove/reorder blocks, edit settings, switch themes) |
| `@frontlit/page-builder/data-slots` | Server-safe helper for hosts to resolve named data slots before rendering |

`createDefaultLayout()` / `getDefaultTheme()` (exported from the root) build a ready-to-render starter landing
page.
133 changes: 133 additions & 0 deletions frontlit/docs/components/text-editor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: Text editor
description: Install and use the TipTap-based rich text editor and static renderer.
nav_order: 20
---

`@frontlit/text-editor` is a TipTap-based rich text editor and static renderer for React. It is
framework-agnostic: it depends only on `react` / `react-dom`. No Next.js, no backend, and no media-upload
service.

Uploaded images are embedded as data URLs by default. Hosts can replace that with their own picker via
`ImagePickerContextProvider`.

The page builder's rich-text block uses this package. You can also use it on its own — for example in a
dashboard form, a comment field, or a help-center article editor.

To try the editor, install the package and render `Editor` as shown in [Usage](#usage), or run the [components demo](https://github.com/codelitdev/frontlit/tree/main/examples/components-demo) from the FrontLit repository.

## Installation

```bash
npm i @frontlit/text-editor
# or
pnpm add @frontlit/text-editor
```

Peer dependencies: `react` and `react-dom` (^18 or ^19).

### CSS

Import the compiled stylesheet. It includes Tailwind utilities, fallback tokens for the editor chrome,
ProseMirror/renderer rules, and a small highlight.js theme. Tailwind is **not** required in the host:

```ts
import "@frontlit/text-editor/styles.css";
```

If you already run Tailwind CSS v4 and want to generate the utilities yourself, skip `styles.css`, scan the
package, and import the prose rules (lists, blockquotes, code blocks — these are not utilities):

```css
@import "@frontlit/text-editor/tokens.css";
@import "@frontlit/text-editor/prose.css";
@source "./node_modules/@frontlit/text-editor";
```

## Usage

`Editor` is a client component. Persist the TipTap JSON it emits, then render that JSON with `TextRenderer`.

```tsx
import { Editor, TextRenderer, emptyDoc } from "@frontlit/text-editor";
import "@frontlit/text-editor/styles.css";

<Editor initialContent={emptyDoc} onChange={(json) => save(json)} />;
<TextRenderer json={savedJson} />;
```

A typical host component:

```tsx
"use client";

import { useState } from "react";
import {
Editor,
TextRenderer,
emptyDoc,
type TextEditorContent,
} from "@frontlit/text-editor";
import "@frontlit/text-editor/styles.css";

export function ArticleBody({
initial,
onSave,
}: {
initial?: TextEditorContent;
onSave: (json: TextEditorContent) => void;
}) {
const [doc, setDoc] = useState<TextEditorContent>(initial ?? emptyDoc);

return (
<>
<Editor
initialContent={doc}
placeholder="Write something…"
onChange={(json) => {
setDoc(json);
onSave(json);
}}
/>
<TextRenderer json={doc} />
</>
);
}
```

### Custom image picker

By default the editor embeds uploaded images as data URLs. Wrap the editor with
`ImagePickerContextProvider` to open your own media library instead:

```tsx
import { Editor, ImagePickerContextProvider } from "@frontlit/text-editor";

<ImagePickerContextProvider
pickImage={async () => {
const media = await openMediaLibrary();
if (!media) return null;
return { src: media.url, alt: media.alt };
}}
>
<Editor initialContent={emptyDoc} onChange={save} />
</ImagePickerContextProvider>;
```

### Theming the renderer

`TextRenderer` accepts an optional, loosely-typed `theme` prop (`TextRendererTheme`) to style headings,
paragraphs, and links. Without one it falls back to plain semantic HTML tags. It is structurally typed against
that small theme shape instead of importing `@frontlit/page-builder`, so this package stays usable on its own.

## What's inside

- `Editor` — the WYSIWYG editing surface (toolbar, bubble menu, tables, code blocks with syntax highlighting,
images, links).
- `TextRenderer` — renders saved `TextEditorContent` (TipTap JSON) as static React elements.
- `TextEditorContent` — the TipTap doc-JSON type both of the above operate on.
- `normalizeTextEditorContent`, `extractTextFromTextEditorContent`, `truncateTextEditorContent` — content
utilities (e.g. for building excerpts).
- `emptyDoc`, `createExtensions`, `createId`, `extractHeadings`, `extractTextFromNode` — lower-level building
blocks, exported for advanced use.
- `ImagePickerContextProvider` — optional hook so a host can supply its own image-upload UI.
67 changes: 67 additions & 0 deletions frontlit/docs/developers/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: Authentication
description: Authenticating REST and MCP requests against the FrontLit API.
nav_order: 20
---

The API recognizes three kinds of authentication, resolved in this order:

1. **OAuth bearer token** — issued by FrontLit's OAuth authorization server for
a specific resource, client, user, and explicit scope set. A token belongs to
your account and works in every team you're a member of.
2. **Session cookie** — set by signing in through the web app (passwordless email OTP, via
[better-auth](https://www.better-auth.com)). Used by the dashboard itself; not applicable to server-to-server
callers.
3. **API key** — a team-scoped secret, sent as the `x-frontlit-apikey` header (or `apikey` body field). Create one
from **Team settings → API keys** in the dashboard, or via `POST /teams/:teamId/keys`. The full secret is only
returned once, at creation time — store it securely.

```bash
curl https://api.frontlit.dev/pages \
-H "x-frontlit-apikey: fl_live_..."
```

## Team scope

Session and OAuth requests choose the team they act on with the `X-Frontlit-Team-Id` header (the dashboard sends
this automatically once you've picked a team). If an account belongs to exactly one team, the header can be
omitted. An API key always authenticates as exactly one, fixed team — no header needed, and a header naming another
team is rejected.

```bash
curl https://api.frontlit.dev/pages \
-H "Authorization: Bearer $TOKEN" \
-H "X-Frontlit-Team-Id: team_..."
```

Use `GET /teams` to list the teams the token can reach. Team selection errors:

| Error | Status | Meaning |
| --- | --- | --- |
| `team_required` | 409 | You belong to several teams and sent no header. The body lists your teams. |
| `no_team` | 409 | The account doesn't belong to any team yet. |
| `invalid_team_id` | 400 | No team has that ID. |
| `not_a_team_member` | 403 | You aren't a member of that team (or were removed). |
| `team_mismatch` | 403 | An API key was used with a team other than its own. |

Access follows membership: a team you join later is reachable with the same token, and leaving a team stops access
on the next request.

For a human member, team permissions and OAuth scopes are separate grants.
Browser requests require the member's current team permission. OAuth requests
require both that permission and the token's scope, so a client cannot exceed
the authority of the person who connected it. See [Team members and
permissions](/docs/teams/members) for the dashboard workflow.

## MCP

The remote MCP endpoint accepts an OAuth bearer token or
`x-frontlit-apikey`. It intentionally rejects dashboard session cookies. See
[MCP connections](/docs/developers/mcp) for protocol, client, scope, and
troubleshooting details.

## Provisioning

`POST /provisioning/teams` is a separate, server-to-server-only endpoint for multi-tenant consumers. It's guarded
by a shared secret (`X-Frontlit-Provisioning-Secret`), not an API key — see the [API Reference](https://api.frontlit.app/docs/)
for its request/response shape.
Loading
Loading