Renderer Plugins
Introduction
Section titled “Introduction”StudioCMS renderer plugins provide a way to extend and customize the rendering process of your StudioCMS application. They allow you to modify how content is rendered on the frontend by adding custom components, wrappers, or other modifications to the rendering pipeline.
Creating a Renderer Plugin
Section titled “Creating a Renderer Plugin”The main plugin file
Section titled “The main plugin file”To get started with creating a renderer plugin, you need to define a StudioCMS plugin that registers your custom page type and its associated renderer component. Below is an example of how to create a simple renderer plugin that adds a custom page type with a renderer and an editor component.
import { function definePlugin(options: StudioCMSPlugin): StudioCMSPlugin
Defines a plugin for StudioCMS.
definePlugin } from 'studiocms/plugins';import { const createResolver: (_base: string) => {    resolve: (...path: Array<string>) => string;}
Allows resolving paths relatively to the integration folder easily. Call it like this:
createResolver } from 'astro-integration-kit';import { (alias) interface AstroIntegrationimport AstroIntegration
const resolve: (...path: Array<string>) => string
function createResolver(_base: string): {    resolve: (...path: Array<string>) => string;}
Allows resolving paths relatively to the integration folder easily. Call it like this:
createResolver(import.
The type of import.meta.
If you need to declare that a given property exists on import.meta,
this type may be augmented via interface merging.
meta.ImportMeta.url: string
The absolute file: URL of the module.
This is defined exactly the same as it is in browsers providing the URL of the
current module file.
This enables useful patterns such as relative file loading:
import { readFileSync } from 'node:fs';const buffer = readFileSync(new URL('./data.proto', import.meta.url));
const myPlugin: () => StudioCMSPlugin
function definePlugin(options: StudioCMSPlugin): StudioCMSPlugin
Defines a plugin for StudioCMS.
definePlugin({    StudioCMSPlugin.identifier: string
The identifier of the plugin, usually the package name.
identifier: 'my-plugin',    StudioCMSPlugin.name: string
The name of the plugin, displayed in the StudioCMS Dashboard.
name: 'My Plugin',    StudioCMSPlugin.studiocmsMinimumVersion: string
The minimum version of StudioCMS required for this plugin to function correctly.
This is used to ensure compatibility between the plugin and the StudioCMS core.
It should be a semantic version string (e.g., "1.0.0").
If the plugin is not compatible with the current version of StudioCMS, it should not be loaded.
This is a required field.
studiocmsMinimumVersion: '0.1.0-beta.28',    StudioCMSPlugin.hooks: {    'studiocms:astro:config'?: PluginHook<{        logger: AstroIntegrationLogger;        addIntegrations: (args_0: AstroIntegration | AstroIntegration[]) => void;    }>;    'studiocms:config:setup'?: PluginHook<...>;} & Partial<...>
setRendering: (args_0: {    pageTypes?: {        label: string;        identifier: string;        fields?: ({            name: string;            label: string;            input: "checkbox";            required?: boolean | undefined;            readOnly?: boolean | undefined;            color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;            defaultChecked?: boolean | undefined;            size?: "sm" | "md" | "lg" | undefined;        } | {            name: string;            label: string;            input: "input";            type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        } | {            name: string;            label: string;            input: "textarea";            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        } | {            options: {                value: string;                label: string;                disabled?: boolean | undefined;            }[];            name: string;            label: string;            input: "radio";            required?: boolean | undefined;            readOnly?: boolean | undefined;            color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;            defaultValue?: string | undefined;            direction?: "horizontal" | "vertical" | undefined;        } | {            options: {                value: string;                label: string;                disabled?: boolean | undefined;            }[];            name: string;            label: string;            input: "select";            type?: "search" | "basic" | undefined;            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        } | {            name: string;            label: string;            input: "row";            fields: ({                name: string;                label: string;                input: "checkbox";                required?: boolean | undefined;                readOnly?: boolean | undefined;                color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;                defaultChecked?: boolean | undefined;                size?: "sm" | "md" | "lg" | undefined;            } | {                name: string;                label: string;                input: "input";                type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;                required?: boolean | undefined;                readOnly?: boolean | undefined;                placeholder?: string | undefined;                defaultValue?: string | undefined;            } | {                name: string;                label: string;                input: "textarea";                required?: boolean | undefined;                readOnly?: boolean | undefined;                placeholder?: string | undefined;                defaultValue?: string | undefined;            } | {                options: {                    value: string;                    label: string;                    disabled?: boolean | undefined;                }[];                name: string;                label: string;                input: "radio";                required?: boolean | undefined;                readOnly?: boolean | undefined;                color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;                defaultValue?: string | undefined;                direction?: "horizontal" | "vertical" | undefined;            } | {                options: {                    value: string;                    label: string;                    disabled?: boolean | undefined;                }[];                name: string;                label: string;                input: "select";                type?: "search" | "basic" | undefined;                required?: boolean | undefined;                readOnly?: boolean | undefined;                placeholder?: string | undefined;                defaultValue?: string | undefined;            })[];            required?: boolean | undefined;            readOnly?: boolean | undefined;            alignCenter?: boolean | undefined;            gapSize?: "sm" | "md" | "lg" | undefined;        })[] | undefined;        description?: string | undefined;        pageContentComponent?: string | undefined;        rendererComponent?: string | undefined;        apiEndpoint?: string | undefined;    }[] | undefined;    augments?: ({        type: "component";        components ...
setRendering: (args_0: {    pageTypes?: {        label: string;        identifier: string;        fields?: ({            name: string;            label: string;            input: "checkbox";            required?: boolean | undefined;            readOnly?: boolean | undefined;            color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;            defaultChecked?: boolean | undefined;            size?: "sm" | "md" | "lg" | undefined;        } | {            name: string;            label: string;            input: "input";            type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        } | {            name: string;            label: string;            input: "textarea";            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        } | {            options: {                value: string;                label: string;                disabled?: boolean | undefined;            }[];            name: string;            label: string;            input: "radio";            required?: boolean | undefined;            readOnly?: boolean | undefined;            color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;            defaultValue?: string | undefined;            direction?: "horizontal" | "vertical" | undefined;        } | {            options: {                value: string;                label: string;                disabled?: boolean | undefined;            }[];            name: string;            label: string;            input: "select";            type?: "search" | "basic" | undefined;            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        } | {            name: string;            label: string;            input: "row";            fields: ({                name: string;                label: string;                input: "checkbox";                required?: boolean | undefined;                readOnly?: boolean | undefined;                color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;                defaultChecked?: boolean | undefined;                size?: "sm" | "md" | "lg" | undefined;            } | {                name: string;                label: string;                input: "input";                type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;                required?: boolean | undefined;                readOnly?: boolean | undefined;                placeholder?: string | undefined;                defaultValue?: string | undefined;            } | {                name: string;                label: string;                input: "textarea";                required?: boolean | undefined;                readOnly?: boolean | undefined;                placeholder?: string | undefined;                defaultValue?: string | undefined;            } | {                options: {                    value: string;                    label: string;                    disabled?: boolean | undefined;                }[];                name: string;                label: string;                input: "radio";                required?: boolean | undefined;                readOnly?: boolean | undefined;                color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;                defaultValue?: string | undefined;                direction?: "horizontal" | "vertical" | undefined;            } | {                options: {                    value: string;                    label: string;                    disabled?: boolean | undefined;                }[];                name: string;                label: string;                input: "select";                type?: "search" | "basic" | undefined;                required?: boolean | undefined;                readOnly?: boolean | undefined;                placeholder?: string | undefined;                defaultValue?: string | undefined;            })[];            required?: boolean | undefined;            readOnly?: boolean | undefined;            alignCenter?: boolean | undefined;            gapSize?: "sm" | "md" | "lg" | undefined;        })[] | undefined;        description?: string | undefined;        pageContentComponent?: string | undefined;        rendererComponent?: string | undefined;        apiEndpoint?: string | undefined;    }[] | undefined;    augments?: ({        type: "component";        components ...
pageTypes?: {    label: string;    identifier: string;    fields?: ({        name: string;        label: string;        input: "checkbox";        required?: boolean | undefined;        readOnly?: boolean | undefined;        color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;        defaultChecked?: boolean | undefined;        size?: "sm" | "md" | "lg" | undefined;    } | {        name: string;        label: string;        input: "input";        type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;        required?: boolean | undefined;        readOnly?: boolean | undefined;        placeholder?: string | undefined;        defaultValue?: string | undefined;    } | {        name: string;        label: string;        input: "textarea";        required?: boolean | undefined;        readOnly?: boolean | undefined;        placeholder?: string | undefined;        defaultValue?: string | undefined;    } | {        options: {            value: string;            label: string;            disabled?: boolean | undefined;        }[];        name: string;        label: string;        input: "radio";        required?: boolean | undefined;        readOnly?: boolean | undefined;        color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;        defaultValue?: string | undefined;        direction?: "horizontal" | "vertical" | undefined;    } | {        options: {            value: string;            label: string;            disabled?: boolean | undefined;        }[];        name: string;        label: string;        input: "select";        type?: "search" | "basic" | undefined;        required?: boolean | undefined;        readOnly?: boolean | undefined;        placeholder?: string | undefined;        defaultValue?: string | undefined;    } | {        name: string;        label: string;        input: "row";        fields: ({            name: string;            label: string;            input: "checkbox";            required?: boolean | undefined;            readOnly?: boolean | undefined;            color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;            defaultChecked?: boolean | undefined;            size?: "sm" | "md" | "lg" | undefined;        } | {            name: string;            label: string;            input: "input";            type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        } | {            name: string;            label: string;            input: "textarea";            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        } | {            options: {                value: string;                label: string;                disabled?: boolean | undefined;            }[];            name: string;            label: string;            input: "radio";            required?: boolean | undefined;            readOnly?: boolean | undefined;            color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;            defaultValue?: string | undefined;            direction?: "horizontal" | "vertical" | undefined;        } | {            options: {                value: string;                label: string;                disabled?: boolean | undefined;            }[];            name: string;            label: string;            input: "select";            type?: "search" | "basic" | undefined;            required?: boolean | undefined;            readOnly?: boolean | undefined;            placeholder?: string | undefined;            defaultValue?: string | undefined;        })[];        required?: boolean | undefined;        readOnly?: boolean | undefined;        alignCenter?: boolean | undefined;        gapSize?: "sm" | "md" | "lg" | undefined;    })[] | undefined;    description?: string | undefined;    pageContentComponent?: string | undefined;    rendererComponent?: string | undefined;    apiEndpoint?: string | undefined;}[] | undefined
identifier: string
label: string
rendererComponent?: string | undefined
const resolve: (...path: Array<string>) => string
pageContentComponent?: string | undefined
const resolve: (...path: Array<string>) => string
The renderer component
Section titled “The renderer component”For the renderer component, you need to create a JavaScript or TypeScript file (if you have a build step) that exports an object conforming to the PluginRenderer type. This object should include the rendering logic for your custom page type.
import type { (alias) interface PluginRendererimport PluginRenderer
Represents a plugin renderer with optional sanitization and rendering logic.
PluginRenderer } from 'studiocms/types';
const const render: {    name: string;    renderer: (content: string) => Promise<string>;    sanitizeOpts: {};}
PluginRenderer.name: string
PluginRenderer.renderer?: GenericAsyncFn<string, string>
content: string
content: string
PluginRenderer.sanitizeOpts?: SanitizeOptions
(alias) interface PluginRendererimport PluginRenderer
Represents a plugin renderer with optional sanitization and rendering logic.
PluginRenderer;
export default const render: {    name: string;    renderer: (content: string) => Promise<string>;    sanitizeOpts: {};}
The Editor component
Section titled “The Editor component”For the editor component, you need to create an Astro component that provides a user interface for editing the content of your custom page type. This component will receive the current content as a prop and should emit updates to the content as the user makes changes. The <textarea> below is used in the final page edit form, allowing users to edit the content directly. So any changes made in any custom editor component should update the value of this <textarea> to ensure the content is saved correctly.
---import type { PluginPageTypeEditorProps } from 'studiocms/types';
interface Props extends PluginPageTypeEditorProps {}
const { content } = Astro.props;---<div class="editor-container">    <textarea id="page-content" name="page-content">{content}</textarea></div>