diff --git a/docs/admin/overview.mdx b/docs/admin/overview.mdx
index faefb6c5f4..e8a9717d47 100644
--- a/docs/admin/overview.mdx
+++ b/docs/admin/overview.mdx
@@ -39,6 +39,7 @@ TODO: add `vite` and `bundler` properties to the table below
| `dateFormat` | Global date format that will be used for all dates in the Admin panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More](/docs/authentication/config). |
+| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
| `components` | Component overrides that affect the entirety of the Admin panel. [More](/docs/admin/components) |
| `webpack` | Customize the Webpack config that's used to generate the Admin panel. [More](/docs/admin/webpack) |
| **`logoutRoute`** | The route for the `logout` page. |
diff --git a/docs/configuration/collections.mdx b/docs/configuration/collections.mdx
index f03b0b66d1..75e84320fe 100644
--- a/docs/configuration/collections.mdx
+++ b/docs/configuration/collections.mdx
@@ -78,6 +78,7 @@ You can customize the way that the Admin panel behaves on a collection-by-collec
| `enableRichTextLink` | The [Rich Text](/docs/fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](/docs/fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `preview` | Function to generate preview URLS within the Admin panel that can point to your app. [More](#preview). |
+| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
| `components` | Swap in your own React components to be used within this collection. [More](/docs/admin/components#collections) |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More](#list-searchable-fields) |
diff --git a/docs/configuration/globals.mdx b/docs/configuration/globals.mdx
index c92da8cc99..25959b754a 100644
--- a/docs/configuration/globals.mdx
+++ b/docs/configuration/globals.mdx
@@ -65,13 +65,14 @@ You can find an [example Global config](https://github.com/payloadcms/public-dem
You can customize the way that the Admin panel behaves on a Global-by-Global basis by defining the `admin` property on a Global's config.
-| Option | Description |
-| ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
-| `group` | Text used as a label for grouping collection and global links together in the navigation. |
-| `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. |
-| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
-| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
-| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
+| Option | Description |
+| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
+| `group` | Text used as a label for grouping collection and global links together in the navigation. |
+| `hidden` | Set to true or a function, called with the current user, returning true to exclude this global from navigation and admin routing. |
+| `components` | Swap in your own React components to be used within this Global. [More](/docs/admin/components#globals) |
+| `preview` | Function to generate a preview URL within the Admin panel for this global that can point to your app. [More](#preview). |
+| `livePreview`| Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
+| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
### Preview
diff --git a/docs/live-preview/frontend.mdx b/docs/live-preview/frontend.mdx
index db231a5019..ceaf274c2a 100644
--- a/docs/live-preview/frontend.mdx
+++ b/docs/live-preview/frontend.mdx
@@ -2,12 +2,147 @@
title: Implementing Live Preview in your app
label: Frontend Implementation
order: 20
-desc: NEED TO WRITE
-keywords: NEED TO WRITE
+desc: Learn how to implement Live Preview in your front-end application.
+keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, useLivePreview
---
-- Talk about in detail how it works on the frontend
-- How you should write your frontend to be "safe" and not break if there is no data filled in a required field, etc.
-- Your UI should be reactive to new props
-- Loading states
-- Etc.
\ No newline at end of file
+While using Live Preview, the Admin panel emits a new `window.postMessage` event every time a change is made to the document. Your front-end application can listen for these events and re-render accordingly.
+
+Wiring your front-end into Live Preview is easy. If your front-end application is built with React or Next.js, use the [`useLivePreview`](#react) React hook that Payload provides. In the future, all other major frameworks like Vue and Svelte will be officially supported. If you are using any of these framework today, you can still easily integrate with Live Preview using the underlying tooling that Payload provides. See [building your own hook](#building-your-own-hook).
+
+By default, all hooks require the following args:
+
+| Path | Description |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`serverURL`** \* | The URL of your Payload server. |
+| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
+| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
+
+_\* An asterisk denotes that a property is required._
+
+And return the following values:
+
+| Path | Description |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`data`** | The live data of the document, merged with the initial data. |
+| **`isLoading`** | A boolean that indicates whether or not the document is loading. |
+
+
+ If your front-end is tightly coupled to required fields, you should ensure that your UI does not break when these fields are removed. For example, if you are rendering something like `data.relatedPosts[0].title`, your page will break once you remove the first related post. To get around this, use conditional logic, optional chaining, or default values in your UI where needed. For example, `data?.relatedPosts?.[0]?.title`.
+
+
+### React
+
+If your front-end application is built with React or Next.js, you can use the `useLivePreview` hook that Payload provides.
+
+First, install the `@payloadcms/live-preview-react` package:
+
+```bash
+npm install @payloadcms/live-preview-react
+```
+
+Then, use the `useLivePreview` hook in your React component:
+
+```tsx
+'use client';
+import { useLivePreview } from '@payloadcms/live-preview-react';
+import { Page as PageType } from '@/payload-types'
+
+// Fetch the page in a server component, pass it to the client component, then thread it through the hook
+// The hook will take over from there and keep the preview in sync with the changes you make
+// The `data` property will contain the live data of the document
+export const PageClient: React.FC<{
+ page: {
+ title: string
+ }
+}> = ({ page: initialPage }) => {
+ const { data } = useLivePreview({
+ initialData: initialPage,
+ serverURL: PAYLOAD_SERVER_URL,
+ depth: 2,
+ })
+
+ return (
+ {data.title}
+ )
+}
+```
+
+
+ If is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../getting-started/concepts#depth) for more information.
+
+
+## Building your own hook
+
+No matter what JS-based front-end framework you are using, you can build your own hook using the underlying tooling that Payload provides.
+
+First, install the base `@payloadcms/live-preview` package:
+
+```bash
+npm install @payloadcms/live-preview
+```
+
+This package provides the following functions:
+
+| Path | Description |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`subscribe`** | Subscribes to the Admin panel's `window.postMessage` events and calls the provided callback function. |
+| **`unsubscribe`** | Unsubscribes from the Admin panel's `window.postMessage` events. |
+
+The `subscribe` function takes the following args:
+
+| Path | Description |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`callback`** \* | A callback function that is called with `data` every time a change is made to the document. |
+| **`serverURL`** \* | The URL of your Payload server. git s |
+| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
+| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
+
+With these functions, you can build your own hook using your front-end framework of choice.
+
+Here is an example of what the same `useLivePreview` React hook from above looks like under the hood:
+
+```tsx
+import { subscribe, unsubscribe } from '@payloadcms/live-preview'
+import { useCallback, useEffect, useState } from 'react'
+
+export const useLivePreview = (props: {
+ depth?: number
+ initialData: T
+ serverURL: string
+}): {
+ data: T
+ isLoading: boolean
+} => {
+ const { depth = 0, initialData, serverURL } = props
+ const [data, setData] = useState(initialData)
+ const [isLoading, setIsLoading] = useState(true)
+
+ const onChange = useCallback((mergedData) => {
+ setData(mergedData)
+ setIsLoading(false)
+ }, [])
+
+ useEffect(() => {
+ const subscription = subscribe({
+ callback: onChange,
+ depth,
+ initialData,
+ serverURL,
+ })
+
+ return () => {
+ unsubscribe(subscription)
+ }
+ }, [serverURL, onChange, depth, initialData])
+
+ return {
+ data,
+ isLoading,
+ }
+}
+```
+
+
+ When building your own hook, ensure that the args and return values are consistent with the ones listed at the top of this document. This will ensure that all hooks follow the same API.
+
diff --git a/docs/live-preview/overview.mdx b/docs/live-preview/overview.mdx
index 4d6e7acf04..a3330aa222 100644
--- a/docs/live-preview/overview.mdx
+++ b/docs/live-preview/overview.mdx
@@ -2,12 +2,102 @@
title: Live Preview
label: Overview
order: 10
-desc: NEED TO WRITE
-keywords: NEED TO WRITE
+desc: With Live Preview you can render your front-end application directly within the Admin panel. Your changes take effect as you type. No save needed.
+keywords: 'live preview', 'preview', 'live', 'iframe', 'iframe preview', 'visual editing', 'design'
---
-TODO: write docs here
-- Show screenshot of the usage
-- Talk about how it works at a high level
-- Talk about the property on `collectionConfig.admin`
-- Talk about the React hook, and the subsequent package that it relies on which can be used for other frameworks
+**With Live Preview you can render your front-end application directly within the Admin panel. As you type, your changes take effect in real-time. No need to save a draft or publish your changes.**
+
+Live Preview works by rendering an iframe on the page that loads your front-end application. The Admin panel communicates with your app through `window.postMessage` events. These events are emitted every time a change is made to the document. Your app then listens for these events and re-renders itself with the data it receives.
+
+{/* IMAGE OF LIVE PREVIEW HERE */}
+
+## Setup
+
+Setting up Live Preview is easy. You first need to enable it through the `admin.components.livePreview` property of your Payload config. It takes the following options:
+
+| Path | Description |
+| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`url`** \* | String, or function that returns a string, pointing to your front-end application. This value is used as the iframe `src`. [More details](#url). |
+| **`breakpoints`** | Array of breakpoints to be used as “device sizes” in the preview window. Each item appears as an option in the toolbar. [More details](#breakpoints). |
+| **`collections`** | Array of collection slugs to enable Live Preview on. |
+| **`globals`** | Array of global slugs to enable Live Preview on. |
+
+_\* An asterisk denotes that a property is required._
+
+Here is a basic example of enabling Live Preview on a `pages` collection:
+
+```ts
+// payload.config.ts
+{
+ admin: {
+ components: {
+ livePreview: {
+ url: 'http://localhost:3000', // The URL to your front-end, this can also be a function (see below)
+ collections: ['pages'], // The collections to enable Live Preview on (globals are also possible)
+ },
+ },
+ }
+}
+```
+
+Once configured, a new "Live Preview" tab will appear at the top of enabled documents. Navigating to this tab opens the preview window and loads your front-end application.
+
+
+ You can also define the admin.components.livePreview property on individual collection and global configs. Settings defined here will be merged into the top-level (if defined) as overrides.
+
+
+### URL
+
+The `url` property is a string that points to your front-end application. This value is used as the `src` attribute of the iframe rendering your front-end.
+
+You can also pass a function in order to dynamically format URLs. This function is called with the following arguments:
+
+| Path | Description |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`data`** | The data of the document being edited. This includes changes that have not yet been saved. |
+| **`documentInfo`** | Information about the document being edited like collection slug. [More details](../admin/hooks#usedocumentinfo). |
+| **`locale`** | The locale currently being edited (if applicable). [More details](../configuration/localization). |
+
+Here is an example of using a function that returns a dynamic URL:
+
+```ts
+// payload.config.ts
+{
+ admin: {
+ components: {
+ livePreview: {
+ url: ({
+ data,
+ documentInfo,
+ locale
+ }) => `${data.tenant.url}${ // Multi-tenant top-level domain
+ documentInfo.slug === 'posts' ? `/posts/${data.slug}` : `/${data.slug}
+ `}?locale=${locale}`, // Localization query param
+ collections: ['pages'],
+ },
+ },
+ }
+}
+```
+
+### Breakpoints
+
+The breakpoints property is an array of objects which are used as “device sizes” in the preview window. Each item will render as an option in the toolbar. When selected, the preview window will resize to the exact dimensions specified in that breakpoint. Each breakpoint takes the following properties:
+
+| Path | Description |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`label`** \* | The label to display in the drop-down. This is what the user will see. |
+| **`name`** \* | The name of the breakpoint. |
+| **`width`** \* | The width of the breakpoint. This is used to set the width of the iframe. |
+| **`height`** \* | The height of the breakpoint. This is used to set the height of the iframe. |
+
+_\* An asterisk denotes that a property is required._
+
+{/* IMAGE OF TOOLBAR HERE */}
+
+The "Responsive" option is always available in the drop-down and requires no additional configuration. This is the default breakpoint that will be used on initial load. This option styles the iframe with a width and height of `100%` so that it fills the screen at its maximum size and automatically resizes as the window changes size.
+
+You may also explicitly resize the Live Preview by using the corresponding inputs in the toolbar. This will temporarily override the breakpoint selection to "Custom" until a predefined breakpoint is selected once again.
+
+If you prefer to freely resize the Live Preview without the use of breakpoints, you can open it in a new window by clicking the button in the toolbar. This will close the iframe and open a new window which can be resized as you wish. Closing it will automatically re-open the iframe.