---
title: "Leiho for SvelteKit: visual editing on WordPress"
description: "Load pages in +page.server.ts, start the bridge from the root layout, spread field() on your elements."
url: https://leiho.dev/frameworks/sveltekit
lang: en
updated: 2026-11-04
---
SvelteKit · Svelte 5

# Visual editing for SvelteKit, on WordPress.

Load pages in +page.server.ts, start the bridge from the root layout, spread field() on your elements.

1. 01
   ## Allow the editor to display your site
   Copy this into src/hooks.server.ts.
   ```ts
   import type { Handle } from '@sveltejs/kit';
   
   export const handle: Handle = async ({ event, resolve }) => {
     const response = await resolve(event);
     response.headers.set('Content-Security-Policy', "frame-ancestors 'self' https://cms.example.com");
     return response;
   };
   ```
   Prerendered pages are served as static files: set the same header on your host.
2. 02
   ## Add the bridge
   Install the package, then load the bridge from the root layout.
   ```Terminal
   npm install leiho
   ```
   ```svelte
   <script>
     import { onMount } from 'svelte';
   
     let { children } = $props();
   
     onMount(() => {
       if (window.self === window.top) return;
       import('leiho').then(({ initLeihoBridge }) => {
         // Already on screen: don't hide the page.
         initLeihoBridge({ wpOrigin: 'https://cms.example.com', cloakTimeout: 0 });
       });
     });
   </script>
   
   {@render children()}
   ```
3. 03
   ## Read the content
   Load each page in +page.server.ts.
   ```ts
   import { error } from '@sveltejs/kit';
   import { loadLeihoContent, indexLeihoContent } from 'leiho/content';
   
   export async function load({ url }) {
     const { data } = await loadLeihoContent({ url: 'https://cms.example.com/wp-json/leiho/v1/export' });
     const page = indexLeihoContent(data).byPath(url.pathname);
     if (!page) error(404, 'Not found');
     return { page };
   }
   ```
   Serverless host with a read-only disk? Pass snapshot: false to loadLeihoContent.
4. 04
   ## Mark what editors can change
   Spread field() on each element editors should change. Always render the element, even when its value is empty.
   ```Svelte
   <script>
     import { field, entry } from 'leiho';
     // field() writes data-wp-post + data-wp-field, entry() names a section.
   // Flexible content rows: block(page, 'sections', i, 'Label').
     let { page } = $props();
   </script>
   
   <section {...entry(page, 'Hero')}>
     <h1 {...field(page, 'hero_title')}>{page.fields.hero_title}</h1>
     <img {...field(page, 'hero_image')} src={page.fields.hero_image?.url} alt={page.fields.hero_image?.alt} />
     {#each page.fields.cards ?? [] as card, i}
       <article {...field(page, `cards.${i}.title`)}>{card.title}</article>
     {/each}
   </section>
   ```

Replace https://cms.example.com with your WordPress address. In wp-admin, **Leiho → Get started** shows these steps with your address filled in, checks each one, and can give the whole setup to your coding agent as one prompt.

Other frameworks

- [**Next.js**](https://leiho.dev/frameworks/nextjs)
- [**Astro**](https://leiho.dev/frameworks/astro)
- [**Nuxt**](https://leiho.dev/frameworks/nuxt)
- [**React**](https://leiho.dev/frameworks/react)
- [**HTML, PHP and others**](https://leiho.dev/frameworks/html)

## Edit the real site.

Free and open source. Try it on a real WordPress, in your browser.

- [Try it in your browser](https://leiho.dev/try)
- [View on GitHub](https://github.com/pixelersagency/leiho)
