Editor Placements
Placements embed custom UI into the Foir editor. You can add a full-page editor tab or a sidebar panel that loads your own web application inside an iframe.
Placement Interface
interface ApplyConfigPlacementInput {
type: string; // Placement type: 'main-editor' or 'sidebar'
url: string; // URL of your custom UI
allowedOrigin?: string; // Origin allowed for postMessage communication
height?: number; // Height of the iframe in pixels
tabName?: string; // Tab label in the editor
hideContentTab?: boolean; // Hide the default content editing tab
modelKeys?: string[]; // Restrict placement to specific models
}Placement Types
main-editor
Adds a tab to the main editor area. When users open a record of a matching model, they see your custom tab alongside (or instead of) the default content tab.
placements: [
{
type: 'main-editor',
url: '/',
modelKeys: ['redirect'],
tabName: 'Redirect Editor',
hideContentTab: true,
},
]sidebar
Adds a panel in the editor sidebar. Sidebar placements appear alongside the built-in sidebar sections.
placements: [
{
type: 'sidebar',
url: '/sidebar',
modelKeys: ['blog-post'],
tabName: 'SEO Tools',
height: 400,
},
]Options
url
The URL loaded in the iframe. This can be a relative path (resolved against your service’s base URL) or an absolute URL.
// Relative -- resolved at push time
{ type: 'main-editor', url: '/' }
// Absolute -- used as-is
{ type: 'main-editor', url: 'https://my-editor.example.com' }modelKeys
Restricts the placement to specific models. If omitted, the placement appears for all models.
// Only show for redirect and vanity-url models
{ type: 'main-editor', url: '/', modelKeys: ['redirect', 'vanity-url'] }hideContentTab
When true, the default content editing tab is hidden. Use this when your custom editor completely replaces the built-in editor.
// Replace the default editor entirely
{
type: 'main-editor',
url: '/',
modelKeys: ['design-system'],
hideContentTab: true,
tabName: 'Design Tokens',
}tabName
The label shown on the editor tab. Defaults to the config name if not specified.
height
Sets the iframe height in pixels. Mainly useful for sidebar placements where you want to control the panel size.
{ type: 'sidebar', url: '/preview', height: 300 }allowedOrigin
The origin allowed for postMessage communication between the editor and your iframe. Set this to enable two-way communication with the Foir editor SDK.
{
type: 'main-editor',
url: 'https://my-editor.example.com',
allowedOrigin: 'https://my-editor.example.com',
}Examples
Custom Main Editor
Replace the default editor with a custom redirect management UI:
import { defineConfig } from '@eide/foir-cli/configs';
export default defineConfig({
key: 'cloudflare-kv',
name: 'Cloudflare KV Redirector',
models: [
{ key: 'redirect', name: 'Redirect', fields: [...] },
],
placements: [
{
type: 'main-editor',
url: '/',
modelKeys: ['redirect'],
hideContentTab: true,
tabName: 'Redirect Editor',
},
],
});Design Token Editor
A custom editor for design system tokens with an external URL and allowed origin:
placements: [
{
type: 'main-editor',
url: 'https://design-editor.example.com',
allowedOrigin: 'https://design-editor.example.com',
tabName: 'Design Tokens',
hideContentTab: true,
modelKeys: ['design-system'],
},
]Sidebar Preview Panel
Add a sidebar panel that shows a live preview of content:
placements: [
{
type: 'sidebar',
url: '/preview',
modelKeys: ['page', 'blog-post'],
tabName: 'Live Preview',
height: 500,
},
]Multiple Placements
You can define multiple placements in the same config:
placements: [
{
type: 'main-editor',
url: '/',
modelKeys: ['product'],
tabName: 'Product Editor',
},
{
type: 'sidebar',
url: '/analytics',
modelKeys: ['product'],
tabName: 'Analytics',
height: 350,
},
]Next Steps
- Editor SDK — Communicate between your placement UI and the Foir editor
- Building Extensions — Build a complete extension with placements
- Configuration Reference — Full config API reference