Skip to content

Making Plugins Useful

Building a StudioCMS Plugin is a powerful way to extend the functionality of StudioCMS. They provide a simple and flexible way to add new features to your StudioCMS project. The following is a basic example of how to create a StudioCMS Plugin and how it works.

To get started, you will need to create a new StudioCMS Plugin. The following is a basic example of the file structure for a StudioCMS Plugin:

  • package.json
  • Directorysrc
    • index.ts
    • Directoryroutes
      • […slug].astro
    • Directorydashboard-grid-items
      • MyPluginGridItem.astro

In the main src/index.ts file, you will define the StudioCMS Plugin. The following is an example of how to define a StudioCMS Plugin that includes an Astro Integration to create a simple blog example:

index.ts
import {
function definePlugin(options: StudioCMSPluginDef): StudioCMSPluginDef

Defines a plugin for StudioCMS.

@paramoptions - The configuration options for the plugin.

@returnsThe plugin configuration.

definePlugin
} from 'studiocms/plugins';
import {
(alias) interface AstroIntegration
import AstroIntegration
AstroIntegration
} from 'astro';
import {
const addVirtualImports: HookUtility<"astro:config:setup", [{
name: string;
imports: Imports;
__enableCorePowerDoNotUseOrYouWillBeFired?: boolean;
}], void>

Creates a Vite virtual module and updates the Astro config. Virtual imports are useful for passing things like config options, or data computed within the integration.

@paramparams

@paramoptions

@paramoptions.name

@paramoptions.imports

@seehttps://astro-integration-kit.netlify.app/utilities/add-virtual-imports/

@example

// my-integration/index.ts
import { addVirtualImports } from "astro-integration-kit";
addVirtualImports(params, {
name: 'my-integration',
imports: {
'virtual:my-integration/config': `export default ${ JSON.stringify({foo: "bar"}) }`,
},
});

This is then readable anywhere else in your integration:

// myIntegration/src/component/layout.astro
import config from "virtual:my-integration/config";
console.log(config.foo) // "bar"

addVirtualImports
,
const createResolver: (_base: string) => {
resolve: (...path: Array<string>) => string;
}

Allows resolving paths relatively to the integration folder easily. Call it like this:

@param_base - The location you want to create relative references from. import.meta.url is usually what you'll want.

@seehttps://astro-integration-kit.netlify.app/core/create-resolver/

@example

const { resolve } = createResolver(import.meta.url);
const pluginPath = resolve("./plugin.ts");

This way, you do not have to add your plugin to your package.json exports.

createResolver
} from 'astro-integration-kit';
// Define the options for the plugin and integration
interface
interface Options
Options
{
Options.route: string
route
: string;
}
export function
function studioCMSPageInjector(options: Options): {
readonly identifier: string;
readonly name: string;
readonly studiocmsMinimumVersion?: string | undefined | undefined;
readonly requires?: readonly string[] | undefined;
readonly hooks: {
"studiocms:astro-config"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>;
}) => Promise<void>) | ((args: {
readonly logger: AstroIntegrationLogger;
readonly addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>;
}) => void) | undefined;
"studiocms:auth"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly setAuthService: (args: {
oAuthProvider: {
readonly name: string;
readonly formattedName: string;
readonly svg: string;
readonly endpointPath: string;
readonly requiredEnvVariables?: readonly string[] | undefined;
};
}) => Promise<void>;
}) => Promise<void>) | ((args: {
readonly logger: AstroIntegrationLogger;
readonly setAuthService: (args: {
oAuthProvider: {
readonly name: string;
readonly formattedName: string;
readonly svg: string;
readonly endpointPath: string;
readonly requiredEnvVariables?: readonly string[] | undefined;
};
}) => Promise<void>;
}) => void) | undefined;
"studiocms:dashboard"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly setDashboard: (args: {
translations: {
[x: string]: {
[x: string]: {
[x: string]: string;
};
};
};
dashboardGridItems?: readonly {
readonly name: string;
readonly span: 1 | 2 | 3;
readonly variant: "default" | "filled";
readonly requiresPermission?: "owner" | "admin" | "editor" | "visitor" | undefined;
readonly header?: {
readonly title: string;
readonly icon?: "heroicons:academic-cap" | "heroicons:academic-cap-16-solid" | "heroicons:academic-cap-20-solid" | "heroicons:academic-cap-solid" | "heroicons:adjustments-horizontal" | "heroicons:adjustments-horizontal-16-solid" | "heroicons:adjustments-horizontal-20-solid" | "heroicons:adjustments-horizontal-solid" | "heroicons:adjustments-vertical" | "heroicons:adjustments-vertical-16-solid" | "heroicons:adjustments-vertical-20-solid" | "heroicons:adjustments-vertical-solid" | "heroicons:archive-box" | "heroicons:archive-box-16-solid" | "heroicons:archive-box-20-solid" | "heroicons:archive-box-arrow-down" | "heroicons:archive-box-arrow-down-16-solid" | "heroicons:archive-box-arrow-down-20-solid" | "heroicons:archive-box-arrow-down-solid" | "heroicons:archive-box-solid" | "heroicons:archive-box-x-mark" | "heroicons:archive-box-x-mark-16-solid" | "heroicons:archive-box-x-mark-20-solid" | "heroicons:archive-box-x-mark-solid" | "heroicons:arrow-down" | "heroicons:arrow-down-16-solid" | "heroicons:arrow-down-20-solid" | "heroicons:arrow-down-circle" | "heroicons:arrow-down-circle-16-solid" | "heroicons:arrow-down-circle-20-solid" | "heroicons:arrow-down-circle-solid" | "heroicons:arrow-down-left" | "heroicons:arrow-down-left-16-solid" | "heroicons:arrow-down-left-20-solid" | "heroicons:arrow-down-left-solid" | "heroicons:arrow-down-on-square" | "heroicons:arrow-down-on-square-16-solid" | "heroicons:arrow-down-on-square-20-solid" | "heroicons:arrow-down-on-square-solid" | "heroicons:arrow-down-on-square-stack" | "heroicons:arrow-down-on-square-stack-16-solid" | "heroicons:arrow-down-on-square-stack-20-solid" | "heroicons:arrow-down-on-square-stack-solid" | "heroicons:arrow-down-right" | "heroicons:arrow-down-right-16-solid" | "heroicons:arrow-down-right-20-solid" | "heroicons:arrow-down-right-solid" | "heroicons:arrow-down-solid" | "heroicons:arrow-down-tray" | "heroicons:arrow-down-tray-16-solid" | "heroicons:arrow-down-tray-20-solid" | "heroicons:arrow-down-tray-solid" | "heroicons:arrow-left" | "heroicons:arrow-left-16-solid" | "heroicons:arrow-left-20-solid" | "heroicons:arrow-left-circle" | "heroicons:arrow-left-circle-16-solid" | "heroicons:arrow-left-circle-20-solid" | "heroicons:arrow-left-circle-solid" | "heroicons:arrow-left-end-on-rectangle" | "heroicons:arrow-left-end-on-rectangle-16-solid" | "heroicons:arrow-left-end-on-rectangle-20-solid" | "heroicons:arrow-left-end-on-rectangle-solid" | "heroicons:arrow-left-on-rectangle" | "heroicons:arrow-left-on-rectangle-20-solid" | "heroicons:arrow-left-on-rectangle-solid" ...
studioCMSPageInjector
(
options: Options
options
:
interface Options
Options
) {
// Resolve the path to the current file
const {
const resolve: (...path: Array<string>) => string
resolve
} =
function createResolver(_base: string): {
resolve: (...path: Array<string>) => string;
}

Allows resolving paths relatively to the integration folder easily. Call it like this:

@param_base - The location you want to create relative references from. import.meta.url is usually what you'll want.

@seehttps://astro-integration-kit.netlify.app/core/create-resolver/

@example

const { resolve } = createResolver(import.meta.url);
const pluginPath = resolve("./plugin.ts");

This way, you do not have to add your plugin to your package.json exports.

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));

url
);
// Define the Astro integration
function
function (local function) myIntegration(options: Options): AstroIntegration
myIntegration
(
options: Options
options
:
interface Options
Options
):
(alias) interface AstroIntegration
import AstroIntegration
AstroIntegration
{
const
const route: string
route
= `/${
options: Options
options
?.
Options.route: string
route
|| 'my-plugin'}`;
return {
AstroIntegration.name: string

The name of the integration.

name
: 'my-astro-integration',
AstroIntegration.hooks: {
'astro:config:setup'?: (options: {
config: AstroConfig;
command: "dev" | "build" | "preview" | "sync";
isRestart: boolean;
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
addRenderer: (renderer: AstroRenderer) => void;
addWatchFile: (path: URL | string) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
injectRoute: (injectRoute: InjectedRoute) => void;
addClientDirective: (directive: ClientDirectiveConfig) => void;
addDevToolbarApp: (entrypoint: DevToolbarAppEntry) => void;
addMiddleware: (mid: AstroIntegrationMiddleware) => void;
createCodegenDir: () => URL;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
... 10 more ...;
'astro:routes:resolved'?: (options: {
routes: IntegrationResolvedRoute[];
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
} & Partial<...>

The different hooks available to extend.

hooks
: {
"astro:config:setup": (
params: {
config: AstroConfig;
command: "dev" | "build" | "preview" | "sync";
isRestart: boolean;
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
addRenderer: (renderer: AstroRenderer) => void;
addWatchFile: (path: URL | string) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
injectRoute: (injectRoute: InjectedRoute) => void;
... 4 more ...;
logger: AstroIntegrationLogger;
}
params
) => {
const {
const injectRoute: (injectRoute: InjectedRoute) => void
injectRoute
} =
params: {
config: AstroConfig;
command: "dev" | "build" | "preview" | "sync";
isRestart: boolean;
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
addRenderer: (renderer: AstroRenderer) => void;
addWatchFile: (path: URL | string) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
injectRoute: (injectRoute: InjectedRoute) => void;
... 4 more ...;
logger: AstroIntegrationLogger;
}
params
;
// Inject the route for the plugin
const injectRoute: (injectRoute: InjectedRoute) => void
injectRoute
({
entrypoint: string | URL
entrypoint
:
const resolve: (...path: Array<string>) => string
resolve
('./routes/[...slug].astro'),
pattern: string
pattern
: `/${
const route: string
route
}/[...slug]`,
prerender?: boolean
prerender
: false,
})
function addVirtualImports(params: {
config: AstroConfig;
command: "dev" | "build" | "preview" | "sync";
isRestart: boolean;
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
addRenderer: (renderer: AstroRenderer) => void;
addWatchFile: (path: URL | string) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
injectRoute: (injectRoute: InjectedRoute) => void;
... 4 more ...;
logger: AstroIntegrationLogger;
}, args_0: {
...;
}): void

Creates a Vite virtual module and updates the Astro config. Virtual imports are useful for passing things like config options, or data computed within the integration.

@paramparams

@paramoptions

@paramoptions.name

@paramoptions.imports

@seehttps://astro-integration-kit.netlify.app/utilities/add-virtual-imports/

@example

// my-integration/index.ts
import { addVirtualImports } from "astro-integration-kit";
addVirtualImports(params, {
name: 'my-integration',
imports: {
'virtual:my-integration/config': `export default ${ JSON.stringify({foo: "bar"}) }`,
},
});

This is then readable anywhere else in your integration:

// myIntegration/src/component/layout.astro
import config from "virtual:my-integration/config";
console.log(config.foo) // "bar"

addVirtualImports
(
params: {
config: AstroConfig;
command: "dev" | "build" | "preview" | "sync";
isRestart: boolean;
updateConfig: (newConfig: DeepPartial<AstroConfig>) => AstroConfig;
addRenderer: (renderer: AstroRenderer) => void;
addWatchFile: (path: URL | string) => void;
injectScript: (stage: InjectedScriptStage, content: string) => void;
injectRoute: (injectRoute: InjectedRoute) => void;
... 4 more ...;
logger: AstroIntegrationLogger;
}
params
, {
name: string
name
: 'my-astro-integration',
imports: Imports
imports
: {
'myplugin:config': `
export const options = ${
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
route: string
route
})};
export default options;
`,
}
})
}
}
}
}
// Define the StudioCMS Plugin
return
function definePlugin(options: StudioCMSPluginDef): StudioCMSPluginDef

Defines a plugin for StudioCMS.

@paramoptions - The configuration options for the plugin.

@returnsThe plugin configuration.

definePlugin
({
identifier: string
identifier
: 'my-plugin',
name: string
name
: 'My Plugin',
hooks: {
"studiocms:astro-config"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>;
}) => Promise<void>) | ((args: {
readonly logger: AstroIntegrationLogger;
readonly addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>;
}) => void) | undefined;
... 5 more ...;
"studiocms:sitemap"?: ((args: {
readonly logger: AstroIntegrationLogger;
readonly setSitemap: (args: {
triggerSitemap?: boolean | undefined;
sitemaps?: readonly {
readonly pluginName: string;
readonly sitemapXMLEndpointPath: string;
}[] | undefined;
}) => Promise<void>;
}) => Promise<void>) | ((args: {
readonly logger: AstroIntegrationLogger;
readonly setSitemap: (args: {
triggerSitemap?: boolean | undefined;
sitemaps?: readonly {
readonly pluginName: string;
readonly sitemapXMLEndpointPath: string;
}[] | undefined;
}) => Promise<void>;
}) => void) | undefined;
}
hooks
: {
'studiocms:astro-config': ({
addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>
addIntegrations
}) => {
addIntegrations: (args: AstroIntegration | AstroIntegration[]) => Promise<void>
addIntegrations
(
function (local function) myIntegration(options: Options): AstroIntegration
myIntegration
(
options: Options
options
)); // Optional, but recommended
},
'studiocms:dashboard': ({
setDashboard: (args: {
translations: {
[x: string]: {
[x: string]: {
[x: string]: string;
};
};
};
dashboardGridItems?: readonly {
readonly name: string;
readonly span: 1 | 2 | 3;
readonly variant: "default" | "filled";
readonly requiresPermission?: "owner" | "admin" | "editor" | "visitor" | undefined;
readonly header?: {
readonly title: string;
readonly icon?: "heroicons:academic-cap" | "heroicons:academic-cap-16-solid" | "heroicons:academic-cap-20-solid" | "heroicons:academic-cap-solid" | "heroicons:adjustments-horizontal" | "heroicons:adjustments-horizontal-16-solid" | "heroicons:adjustments-horizontal-20-solid" | "heroicons:adjustments-horizontal-solid" | "heroicons:adjustments-vertical" | "heroicons:adjustments-vertical-16-solid" | "heroicons:adjustments-vertical-20-solid" | "heroicons:adjustments-vertical-solid" | "heroicons:archive-box" | "heroicons:archive-box-16-solid" | "heroicons:archive-box-20-solid" | "heroicons:archive-box-arrow-down" | "heroicons:archive-box-arrow-down-16-solid" | "heroicons:archive-box-arrow-down-20-solid" | "heroicons:archive-box-arrow-down-solid" | "heroicons:archive-box-solid" | "heroicons:archive-box-x-mark" | "heroicons:archive-box-x-mark-16-solid" | "heroicons:archive-box-x-mark-20-solid" | "heroicons:archive-box-x-mark-solid" | "heroicons:arrow-down" | "heroicons:arrow-down-16-solid" | "heroicons:arrow-down-20-solid" | "heroicons:arrow-down-circle" | "heroicons:arrow-down-circle-16-solid" | "heroicons:arrow-down-circle-20-solid" | "heroicons:arrow-down-circle-solid" | "heroicons:arrow-down-left" | "heroicons:arrow-down-left-16-solid" | "heroicons:arrow-down-left-20-solid" | "heroicons:arrow-down-left-solid" | "heroicons:arrow-down-on-square" | "heroicons:arrow-down-on-square-16-solid" | "heroicons:arrow-down-on-square-20-solid" | "heroicons:arrow-down-on-square-solid" | "heroicons:arrow-down-on-square-stack" | "heroicons:arrow-down-on-square-stack-16-solid" | "heroicons:arrow-down-on-square-stack-20-solid" | "heroicons:arrow-down-on-square-stack-solid" | "heroicons:arrow-down-right" | "heroicons:arrow-down-right-16-solid" | "heroicons:arrow-down-right-20-solid" | "heroicons:arrow-down-right-solid" | "heroicons:arrow-down-solid" | "heroicons:arrow-down-tray" | "heroicons:arrow-down-tray-16-solid" | "heroicons:arrow-down-tray-20-solid" | "heroicons:arrow-down-tray-solid" | "heroicons:arrow-left" | "heroicons:arrow-left-16-solid" | "heroicons:arrow-left-20-solid" | "heroicons:arrow-left-circle" | "heroicons:arrow-left-circle-16-solid" | "heroicons:arrow-left-circle-20-solid" | "heroicons:arrow-left-circle-solid" | "heroicons:arrow-left-end-on-rectangle" | "heroicons:arrow-left-end-on-rectangle-16-solid" | "heroicons:arrow-left-end-on-rectangle-20-solid" | "heroicons:arrow-left-end-on-rectangle-solid" | "heroicons:arrow-left-on-rectangle" | "heroicons:arrow-left-on-rectangle-20-solid" | "heroicons:arrow-left-on-rectangle-solid" | "heroicons:arrow-left-solid" | "heroicons:arrow-left-start-on-rectangle" | "heroicons:arrow-left-start-on-rectangle-16-solid" | "heroicons:arrow-left-start-on-rectangle-20-solid" | "heroicons:arrow-left-start-on-rectangle-solid" | "heroicons:arrow-long-down" | "heroicons:arrow-long-down-16-solid" | "heroicons:arrow-long-down-20-solid" | "heroicons:arrow-long-down-solid" | "heroicons:arrow-long-left" | "heroicons:arrow-long-left-16-solid" | "heroicons:arrow-long-left-20-solid" | "heroicons:arrow-long-left-solid" | "heroicons:arrow-long-right" | "heroicons:arrow-long-right-16-solid" | "heroicons:arrow-long-right-20-solid" | "heroicons:arrow-long-right-solid" | "heroicons:arrow-long-up" | "heroicons:arrow-long-up-16-solid" | "heroicons:arrow-long-up-20-solid" | "heroicons:arrow-long-up-solid" | "heroicons:arrow-path" | "heroicons:arrow-path-16-solid" | "heroicons:arrow-path-20-solid" | "heroicons:arrow-path-rounded-square" | "heroicons:arrow-path-rounded-square-16-solid" | "heroicons:arrow-path-rounded-square-20-solid" | "heroicons:arrow-path-rounded-square-solid" | "heroicons:arrow-path-solid" | "heroicons:arrow-right" | "heroicons:arrow-right-16-solid" | "heroicons:arrow-right-20-solid" | "heroicons:arrow-right-circle" | "heroicons:arrow-right-circle-16-solid" | "heroicons:arrow-right-circle-20-solid" | "heroicons:arrow-right-circle-solid" | "heroicons:arrow-right-end-on-rectangle" | "heroicons:arrow-right-end-on-rectangle-16-solid" | "heroicons:arrow-right-end-on-rectangle-20-solid" | "heroicons:arrow-right-end-on-rectangle-solid" | "heroicons:arrow-right-on-rectangle" | "heroicons:arrow-right-on-rectangle-20-solid" | "heroicons:arrow-right-on-rectangle-solid" | "heroicons:arrow-right-solid" | "heroicons:arrow-right-start-on-rectangle" | "heroicons:arrow-right-start-on-rectangle-16-solid" | "heroicons:arrow-right-start-on-rectangle-20-solid" | "heroicons:arrow-right-start-on-rectangle-solid" | "heroicons:arrow-small-down" | "heroicons:arrow-small-down-20-solid" ...
setDashboard
}) => {
setDashboard: (args: {
translations: {
[x: string]: {
[x: string]: {
[x: string]: string;
};
};
};
dashboardGridItems?: readonly {
readonly name: string;
readonly span: 1 | 2 | 3;
readonly variant: "default" | "filled";
readonly requiresPermission?: "owner" | "admin" | "editor" | "visitor" | undefined;
readonly header?: {
readonly title: string;
readonly icon?: "heroicons:academic-cap" | "heroicons:academic-cap-16-solid" | "heroicons:academic-cap-20-solid" | "heroicons:academic-cap-solid" | "heroicons:adjustments-horizontal" | "heroicons:adjustments-horizontal-16-solid" | "heroicons:adjustments-horizontal-20-solid" | "heroicons:adjustments-horizontal-solid" | "heroicons:adjustments-vertical" | "heroicons:adjustments-vertical-16-solid" | "heroicons:adjustments-vertical-20-solid" | "heroicons:adjustments-vertical-solid" | "heroicons:archive-box" | "heroicons:archive-box-16-solid" | "heroicons:archive-box-20-solid" | "heroicons:archive-box-arrow-down" | "heroicons:archive-box-arrow-down-16-solid" | "heroicons:archive-box-arrow-down-20-solid" | "heroicons:archive-box-arrow-down-solid" | "heroicons:archive-box-solid" | "heroicons:archive-box-x-mark" | "heroicons:archive-box-x-mark-16-solid" | "heroicons:archive-box-x-mark-20-solid" | "heroicons:archive-box-x-mark-solid" | "heroicons:arrow-down" | "heroicons:arrow-down-16-solid" | "heroicons:arrow-down-20-solid" | "heroicons:arrow-down-circle" | "heroicons:arrow-down-circle-16-solid" | "heroicons:arrow-down-circle-20-solid" | "heroicons:arrow-down-circle-solid" | "heroicons:arrow-down-left" | "heroicons:arrow-down-left-16-solid" | "heroicons:arrow-down-left-20-solid" | "heroicons:arrow-down-left-solid" | "heroicons:arrow-down-on-square" | "heroicons:arrow-down-on-square-16-solid" | "heroicons:arrow-down-on-square-20-solid" | "heroicons:arrow-down-on-square-solid" | "heroicons:arrow-down-on-square-stack" | "heroicons:arrow-down-on-square-stack-16-solid" | "heroicons:arrow-down-on-square-stack-20-solid" | "heroicons:arrow-down-on-square-stack-solid" | "heroicons:arrow-down-right" | "heroicons:arrow-down-right-16-solid" | "heroicons:arrow-down-right-20-solid" | "heroicons:arrow-down-right-solid" | "heroicons:arrow-down-solid" | "heroicons:arrow-down-tray" | "heroicons:arrow-down-tray-16-solid" | "heroicons:arrow-down-tray-20-solid" | "heroicons:arrow-down-tray-solid" | "heroicons:arrow-left" | "heroicons:arrow-left-16-solid" | "heroicons:arrow-left-20-solid" | "heroicons:arrow-left-circle" | "heroicons:arrow-left-circle-16-solid" | "heroicons:arrow-left-circle-20-solid" | "heroicons:arrow-left-circle-solid" | "heroicons:arrow-left-end-on-rectangle" | "heroicons:arrow-left-end-on-rectangle-16-solid" | "heroicons:arrow-left-end-on-rectangle-20-solid" | "heroicons:arrow-left-end-on-rectangle-solid" | "heroicons:arrow-left-on-rectangle" | "heroicons:arrow-left-on-rectangle-20-solid" | "heroicons:arrow-left-on-rectangle-solid" | "heroicons:arrow-left-solid" | "heroicons:arrow-left-start-on-rectangle" | "heroicons:arrow-left-start-on-rectangle-16-solid" | "heroicons:arrow-left-start-on-rectangle-20-solid" | "heroicons:arrow-left-start-on-rectangle-solid" | "heroicons:arrow-long-down" | "heroicons:arrow-long-down-16-solid" | "heroicons:arrow-long-down-20-solid" | "heroicons:arrow-long-down-solid" | "heroicons:arrow-long-left" | "heroicons:arrow-long-left-16-solid" | "heroicons:arrow-long-left-20-solid" | "heroicons:arrow-long-left-solid" | "heroicons:arrow-long-right" | "heroicons:arrow-long-right-16-solid" | "heroicons:arrow-long-right-20-solid" | "heroicons:arrow-long-right-solid" | "heroicons:arrow-long-up" | "heroicons:arrow-long-up-16-solid" | "heroicons:arrow-long-up-20-solid" | "heroicons:arrow-long-up-solid" | "heroicons:arrow-path" | "heroicons:arrow-path-16-solid" | "heroicons:arrow-path-20-solid" | "heroicons:arrow-path-rounded-square" | "heroicons:arrow-path-rounded-square-16-solid" | "heroicons:arrow-path-rounded-square-20-solid" | "heroicons:arrow-path-rounded-square-solid" | "heroicons:arrow-path-solid" | "heroicons:arrow-right" | "heroicons:arrow-right-16-solid" | "heroicons:arrow-right-20-solid" | "heroicons:arrow-right-circle" | "heroicons:arrow-right-circle-16-solid" | "heroicons:arrow-right-circle-20-solid" | "heroicons:arrow-right-circle-solid" | "heroicons:arrow-right-end-on-rectangle" | "heroicons:arrow-right-end-on-rectangle-16-solid" | "heroicons:arrow-right-end-on-rectangle-20-solid" | "heroicons:arrow-right-end-on-rectangle-solid" | "heroicons:arrow-right-on-rectangle" | "heroicons:arrow-right-on-rectangle-20-solid" | "heroicons:arrow-right-on-rectangle-solid" | "heroicons:arrow-right-solid" | "heroicons:arrow-right-start-on-rectangle" | "heroicons:arrow-right-start-on-rectangle-16-solid" | "heroicons:arrow-right-start-on-rectangle-20-solid" | "heroicons:arrow-right-start-on-rectangle-solid" | "heroicons:arrow-small-down" | "heroicons:arrow-small-down-20-solid" ...
setDashboard
({
translations: {
[x: string]: {
[x: string]: {
[x: string]: string;
};
};
}
translations
: {
en: {
example: {
title: string;
'other-text': string;
};
}
en
: {
example: {
title: string;
'other-text': string;
}
example
: {
title: string
title
: 'Example',
'other-text': 'Some other text',
}
},
fr: {
example: {
title: string;
'other-text': string;
};
}
fr
: {
example: {
title: string;
'other-text': string;
}
example
: {
title: string
title
: 'Exemple',
'other-text': 'Un autre texte',
}
}
},
// Define the grid items for the dashboard
// These are the items that will be displayed on the StudioCMS Dashboard
// You can define as many items as you want
// In this example, we are defining a single item, which has a span of 2 and requires the 'editor' permission and injects an Astro component which replaces the plain html custom element.
dashboardGridItems?: readonly {
readonly name: string;
readonly span: 1 | 2 | 3;
readonly variant: "default" | "filled";
readonly requiresPermission?: "owner" | "admin" | "editor" | "visitor" | undefined;
readonly header?: {
readonly title: string;
readonly icon?: "heroicons:academic-cap" | "heroicons:academic-cap-16-solid" | "heroicons:academic-cap-20-solid" | "heroicons:academic-cap-solid" | "heroicons:adjustments-horizontal" | "heroicons:adjustments-horizontal-16-solid" | "heroicons:adjustments-horizontal-20-solid" | "heroicons:adjustments-horizontal-solid" | "heroicons:adjustments-vertical" | "heroicons:adjustments-vertical-16-solid" | "heroicons:adjustments-vertical-20-solid" | "heroicons:adjustments-vertical-solid" | "heroicons:archive-box" | "heroicons:archive-box-16-solid" | "heroicons:archive-box-20-solid" | "heroicons:archive-box-arrow-down" | "heroicons:archive-box-arrow-down-16-solid" | "heroicons:archive-box-arrow-down-20-solid" | "heroicons:archive-box-arrow-down-solid" | "heroicons:archive-box-solid" | "heroicons:archive-box-x-mark" | "heroicons:archive-box-x-mark-16-solid" | "heroicons:archive-box-x-mark-20-solid" | "heroicons:archive-box-x-mark-solid" | "heroicons:arrow-down" | "heroicons:arrow-down-16-solid" | "heroicons:arrow-down-20-solid" | "heroicons:arrow-down-circle" | "heroicons:arrow-down-circle-16-solid" | "heroicons:arrow-down-circle-20-solid" | "heroicons:arrow-down-circle-solid" | "heroicons:arrow-down-left" | "heroicons:arrow-down-left-16-solid" | "heroicons:arrow-down-left-20-solid" | "heroicons:arrow-down-left-solid" | "heroicons:arrow-down-on-square" | "heroicons:arrow-down-on-square-16-solid" | "heroicons:arrow-down-on-square-20-solid" | "heroicons:arrow-down-on-square-solid" | "heroicons:arrow-down-on-square-stack" | "heroicons:arrow-down-on-square-stack-16-solid" | "heroicons:arrow-down-on-square-stack-20-solid" | "heroicons:arrow-down-on-square-stack-solid" | "heroicons:arrow-down-right" | "heroicons:arrow-down-right-16-solid" | "heroicons:arrow-down-right-20-solid" | "heroicons:arrow-down-right-solid" | "heroicons:arrow-down-solid" | "heroicons:arrow-down-tray" | "heroicons:arrow-down-tray-16-solid" | "heroicons:arrow-down-tray-20-solid" | "heroicons:arrow-down-tray-solid" | "heroicons:arrow-left" | "heroicons:arrow-left-16-solid" | "heroicons:arrow-left-20-solid" | "heroicons:arrow-left-circle" | "heroicons:arrow-left-circle-16-solid" | "heroicons:arrow-left-circle-20-solid" | "heroicons:arrow-left-circle-solid" | "heroicons:arrow-left-end-on-rectangle" | "heroicons:arrow-left-end-on-rectangle-16-solid" | "heroicons:arrow-left-end-on-rectangle-20-solid" | "heroicons:arrow-left-end-on-rectangle-solid" | "heroicons:arrow-left-on-rectangle" | "heroicons:arrow-left-on-rectangle-20-solid" | "heroicons:arrow-left-on-rectangle-solid" | "heroicons:arrow-left-solid" | "heroicons:arrow-left-start-on-rectangle" | "heroicons:arrow-left-start-on-rectangle-16-solid" | "heroicons:arrow-left-start-on-rectangle-20-solid" | "heroicons:arrow-left-start-on-rectangle-solid" | "heroicons:arrow-long-down" | "heroicons:arrow-long-down-16-solid" | "heroicons:arrow-long-down-20-solid" | "heroicons:arrow-long-down-solid" | "heroicons:arrow-long-left" | "heroicons:arrow-long-left-16-solid" | "heroicons:arrow-long-left-20-solid" | "heroicons:arrow-long-left-solid" | "heroicons:arrow-long-right" | "heroicons:arrow-long-right-16-solid" | "heroicons:arrow-long-right-20-solid" | "heroicons:arrow-long-right-solid" | "heroicons:arrow-long-up" | "heroicons:arrow-long-up-16-solid" | "heroicons:arrow-long-up-20-solid" | "heroicons:arrow-long-up-solid" | "heroicons:arrow-path" | "heroicons:arrow-path-16-solid" | "heroicons:arrow-path-20-solid" | "heroicons:arrow-path-rounded-square" | "heroicons:arrow-path-rounded-square-16-solid" | "heroicons:arrow-path-rounded-square-20-solid" | "heroicons:arrow-path-rounded-square-solid" | "heroicons:arrow-path-solid" | "heroicons:arrow-right" | "heroicons:arrow-right-16-solid" | "heroicons:arrow-right-20-solid" | "heroicons:arrow-right-circle" | "heroicons:arrow-right-circle-16-solid" | "heroicons:arrow-right-circle-20-solid" | "heroicons:arrow-right-circle-solid" | "heroicons:arrow-right-end-on-rectangle" | "heroicons:arrow-right-end-on-rectangle-16-solid" | "heroicons:arrow-right-end-on-rectangle-20-solid" | "heroicons:arrow-right-end-on-rectangle-solid" | "heroicons:arrow-right-on-rectangle" | "heroicons:arrow-right-on-rectangle-20-solid" | "heroicons:arrow-right-on-rectangle-solid" | "heroicons:arrow-right-solid" | "heroicons:arrow-right-start-on-rectangle" | "heroicons:arrow-right-start-on-rectangle-16-solid" | "heroicons:arrow-right-start-on-rectangle-20-solid" | "heroicons:arrow-right-start-on-rectangle-solid" | "heroicons:arrow-small-down" | "heroicons:arrow-small-down-20-solid" | "heroicons:arrow-small-down-solid" | "heroicons:arrow-small-left" | "heroicons:arrow-small-left-20-solid" | "heroicons:arrow-small-left-solid" | "heroicons:arrow-small-right" ...
dashboardGridItems
: [
{
name: string
name
: 'example',
span: 1 | 2 | 3
span
: 2,
variant: "default" | "filled"
variant
: 'default',
requiresPermission?: "owner" | "admin" | "editor" | "visitor" | undefined
requiresPermission
: 'editor',
header?: {
readonly title: string;
readonly icon?: "heroicons:academic-cap" | "heroicons:academic-cap-16-solid" | "heroicons:academic-cap-20-solid" | "heroicons:academic-cap-solid" | "heroicons:adjustments-horizontal" | "heroicons:adjustments-horizontal-16-solid" | "heroicons:adjustments-horizontal-20-solid" | "heroicons:adjustments-horizontal-solid" | "heroicons:adjustments-vertical" | "heroicons:adjustments-vertical-16-solid" | "heroicons:adjustments-vertical-20-solid" | "heroicons:adjustments-vertical-solid" | "heroicons:archive-box" | "heroicons:archive-box-16-solid" | "heroicons:archive-box-20-solid" | "heroicons:archive-box-arrow-down" | "heroicons:archive-box-arrow-down-16-solid" | "heroicons:archive-box-arrow-down-20-solid" | "heroicons:archive-box-arrow-down-solid" | "heroicons:archive-box-solid" | "heroicons:archive-box-x-mark" | "heroicons:archive-box-x-mark-16-solid" | "heroicons:archive-box-x-mark-20-solid" | "heroicons:archive-box-x-mark-solid" | "heroicons:arrow-down" | "heroicons:arrow-down-16-solid" | "heroicons:arrow-down-20-solid" | "heroicons:arrow-down-circle" | "heroicons:arrow-down-circle-16-solid" | "heroicons:arrow-down-circle-20-solid" | "heroicons:arrow-down-circle-solid" | "heroicons:arrow-down-left" | "heroicons:arrow-down-left-16-solid" | "heroicons:arrow-down-left-20-solid" | "heroicons:arrow-down-left-solid" | "heroicons:arrow-down-on-square" | "heroicons:arrow-down-on-square-16-solid" | "heroicons:arrow-down-on-square-20-solid" | "heroicons:arrow-down-on-square-solid" | "heroicons:arrow-down-on-square-stack" | "heroicons:arrow-down-on-square-stack-16-solid" | "heroicons:arrow-down-on-square-stack-20-solid" | "heroicons:arrow-down-on-square-stack-solid" | "heroicons:arrow-down-right" | "heroicons:arrow-down-right-16-solid" | "heroicons:arrow-down-right-20-solid" | "heroicons:arrow-down-right-solid" | "heroicons:arrow-down-solid" | "heroicons:arrow-down-tray" | "heroicons:arrow-down-tray-16-solid" | "heroicons:arrow-down-tray-20-solid" | "heroicons:arrow-down-tray-solid" | "heroicons:arrow-left" | "heroicons:arrow-left-16-solid" | "heroicons:arrow-left-20-solid" | "heroicons:arrow-left-circle" | "heroicons:arrow-left-circle-16-solid" | "heroicons:arrow-left-circle-20-solid" | "heroicons:arrow-left-circle-solid" | "heroicons:arrow-left-end-on-rectangle" | "heroicons:arrow-left-end-on-rectangle-16-solid" | "heroicons:arrow-left-end-on-rectangle-20-solid" | "heroicons:arrow-left-end-on-rectangle-solid" | "heroicons:arrow-left-on-rectangle" | "heroicons:arrow-left-on-rectangle-20-solid" | "heroicons:arrow-left-on-rectangle-solid" | "heroicons:arrow-left-solid" | "heroicons:arrow-left-start-on-rectangle" | "heroicons:arrow-left-start-on-rectangle-16-solid" | "heroicons:arrow-left-start-on-rectangle-20-solid" | "heroicons:arrow-left-start-on-rectangle-solid" | "heroicons:arrow-long-down" | "heroicons:arrow-long-down-16-solid" | "heroicons:arrow-long-down-20-solid" | "heroicons:arrow-long-down-solid" | "heroicons:arrow-long-left" | "heroicons:arrow-long-left-16-solid" | "heroicons:arrow-long-left-20-solid" | "heroicons:arrow-long-left-solid" | "heroicons:arrow-long-right" | "heroicons:arrow-long-right-16-solid" | "heroicons:arrow-long-right-20-solid" | "heroicons:arrow-long-right-solid" | "heroicons:arrow-long-up" | "heroicons:arrow-long-up-16-solid" | "heroicons:arrow-long-up-20-solid" | "heroicons:arrow-long-up-solid" | "heroicons:arrow-path" | "heroicons:arrow-path-16-solid" | "heroicons:arrow-path-20-solid" | "heroicons:arrow-path-rounded-square" | "heroicons:arrow-path-rounded-square-16-solid" | "heroicons:arrow-path-rounded-square-20-solid" | "heroicons:arrow-path-rounded-square-solid" | "heroicons:arrow-path-solid" | "heroicons:arrow-right" | "heroicons:arrow-right-16-solid" | "heroicons:arrow-right-20-solid" | "heroicons:arrow-right-circle" | "heroicons:arrow-right-circle-16-solid" | "heroicons:arrow-right-circle-20-solid" | "heroicons:arrow-right-circle-solid" | "heroicons:arrow-right-end-on-rectangle" | "heroicons:arrow-right-end-on-rectangle-16-solid" | "heroicons:arrow-right-end-on-rectangle-20-solid" | "heroicons:arrow-right-end-on-rectangle-solid" | "heroicons:arrow-right-on-rectangle" | "heroicons:arrow-right-on-rectangle-20-solid" | "heroicons:arrow-right-on-rectangle-solid" | "heroicons:arrow-right-solid" | "heroicons:arrow-right-start-on-rectangle" | "heroicons:arrow-right-start-on-rectangle-16-solid" | "heroicons:arrow-right-start-on-rectangle-20-solid" | "heroicons:arrow-right-start-on-rectangle-solid" | "heroicons:arrow-small-down" | "heroicons:arrow-small-down-20-solid" | "heroicons:arrow-small-down-solid" | "heroicons:arrow-small-left" | "heroicons:arrow-small-left-20-solid" | "heroicons:arrow-small-left-solid" | "heroicons:arrow-small-right" | "heroicons:arrow-small-right-20-solid" | "heroicons:arrow-small-right-solid" | "heroicons:arrow-small-up" | "heroicons:arrow-small-up-20-solid" | "heroicons:arrow-small-up-solid" | "heroicons:arrow-top-right-on-square" | "heroicons:arrow-top-right-on-square-16-solid" ...
header
: {
title: string
title
: 'Example',
icon?: "heroicons:academic-cap" | "heroicons:academic-cap-16-solid" | "heroicons:academic-cap-20-solid" | "heroicons:academic-cap-solid" | "heroicons:adjustments-horizontal" | "heroicons:adjustments-horizontal-16-solid" | "heroicons:adjustments-horizontal-20-solid" | "heroicons:adjustments-horizontal-solid" | "heroicons:adjustments-vertical" | "heroicons:adjustments-vertical-16-solid" | "heroicons:adjustments-vertical-20-solid" | "heroicons:adjustments-vertical-solid" | "heroicons:archive-box" | "heroicons:archive-box-16-solid" | ... 1284 more ... | undefined
icon
: 'heroicons:bolt' },
body?: {
readonly html: string;
readonly components?: {
readonly [x: string]: string;
} | undefined;
readonly sanitizeOpts?: SanitizeOptions | undefined;
} | undefined
body
: {
// Always use plain html without `-` or special characters in the tags, they will get replaced with the Astro component and this HTML will never be rendered
html: string
html
: '<examplegriditem></examplegriditem>',
components?: {
readonly [x: string]: string;
} | undefined
components
: {
// Inject the Astro component to replace the plain html custom element
examplegriditem: string
examplegriditem
:
const resolve: (...path: Array<string>) => string
resolve
('./dashboard-grid-items/MyPluginGridItem.astro')
}
}
}
],
});
},
'studiocms:frontend': ({
setFrontend: (args: {
frontendNavigationLinks?: readonly {
readonly label: string;
readonly href: string;
}[] | undefined;
}) => Promise<void>
setFrontend
}) => {
setFrontend: (args: {
frontendNavigationLinks?: readonly {
readonly label: string;
readonly href: string;
}[] | undefined;
}) => Promise<void>
setFrontend
({
// Define the frontend navigation links for the plugin
// This is useful if you are using the built in StudioCMS navigation helpers in your layout,
// such as when using the `@studiocms/blog` Plugin.
frontendNavigationLinks?: readonly {
readonly label: string;
readonly href: string;
}[] | undefined
frontendNavigationLinks
: [{
label: string
label
: 'My Plugin',
href: string
href
:
options: Options
options
?.
Options.route: string
route
|| 'my-plugin' }],
});
},
'studiocms:rendering': ({
setRendering: (args: {
pageTypes?: readonly {
readonly identifier: string;
readonly label: string;
readonly description?: string | undefined;
readonly fields?: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "select";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly type?: "search" | "basic" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly fields: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly ...
setRendering
}) => {
setRendering: (args: {
pageTypes?: readonly {
readonly identifier: string;
readonly label: string;
readonly description?: string | undefined;
readonly fields?: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "select";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly type?: "search" | "basic" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly fields: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly ...
setRendering
({
// When creating pageTypes, you can also define a `pageContentComponent` if your plugin requires a custom content editor.
// pageTypes: [{ identifier: 'my-plugin', label: 'Blog Post (My Plugin)', pageContentComponent: resolve('./components/MyContentEditor.astro') }],
// In this example we are okay using the default content editor (markdown).
pageTypes?: readonly {
readonly identifier: string;
readonly label: string;
readonly description?: string | undefined;
readonly fields?: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "select";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly type?: "search" | "basic" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly fields: readonly ({
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "checkbox";
readonly defaultChecked?: boolean | undefined;
readonly size?: "sm" | "md" | "lg" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "input";
readonly type?: "number" | "text" | "password" | "email" | "tel" | "url" | "search" | undefined;
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly placeholder?: string | undefined;
readonly input: "textarea";
readonly defaultValue?: string | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "radio";
readonly options: {
readonly value: string;
readonly label: string;
readonly disabled?: boolean | undefined;
}[];
readonly defaultValue?: string | undefined;
readonly direction?: "horizontal" | "vertical" | undefined;
} | {
readonly name: string;
readonly label: string;
readonly required?: boolean | undefined;
readonly readOnly?: boolean | undefined;
readonly color?: "primary" | "success" | "warning" | "danger" | "info" | "mono" | undefined;
readonly input: "select";
readonly options: {
readonly value: string;
readonly label: string;
readonly ...
pageTypes
: [{
identifier: string
identifier
: 'my-plugin',
label: string
label
: 'Blog Post (My Plugin)' }],
});
}
}
});
}

The above example defines a StudioCMS Plugin that includes an Astro Integration to create a simple blog example. The plugin includes a route that is injected into the StudioCMS project and a grid item that is displayed on the StudioCMS Dashboard.

For more information on how to create an Astro Integration, see the Astro Integration Kit^ and the Astro Integrations documentation^.

In the src/routes/[...slug].astro file, you will define the route for the plugin. The following is an example of how to define a route for the plugin, we will break this out into two parts, the first part is the frontmatter (between the --- marks), and the second part is the HTML template that gets put under the second ---.

Frontmatter
import {
const StudioCMSRenderer: any
StudioCMSRenderer
} from 'studiocms:renderer';
import {
const SDKCoreJs: {
AUTH: {
verifyEmail: {
get: (input: string) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
readonly token: string;
} | undefined, DBCallbackFailure | DatabaseError, never>;
create: (userId: string) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
readonly token: string;
}, DBCallbackFailure | DatabaseError | GeneratorError, never>;
delete: (input: string) => Effect<DeleteResult, DBCallbackFailure | DatabaseError, never>;
};
oAuth: {
create: (input: {
readonly providerUserId: string;
readonly provider: string;
readonly userId: string;
}) => Effect<{
readonly providerUserId: string;
readonly provider: string;
readonly userId: string;
}, DBCallbackFailure | DatabaseError, never>;
delete: (input: {
readonly userId: string;
readonly provider: string;
}) => AuthDeletionResponse;
searchByProviderId: (input: {
readonly providerUserId: string;
readonly userId: string;
}) => Effect<{
readonly providerUserId: string;
readonly provider: string;
readonly userId: string;
} | undefined, DBCallbackFailure | DatabaseError, never>;
searchProvidersForId: (input: {
readonly userId: string;
readonly providerId: string;
}) => Effect<{
readonly providerUserId: string;
readonly provider: string;
readonly userId: string;
} | undefined, DBCallbackFailure | DatabaseError, never>;
};
permission: {
currentStatus: (input: string) => Effect<{
readonly user: string;
readonly rank: "owner" | "admin" | "editor" | "visitor" | "unknown";
} | undefined, DBCallbackFailure | DatabaseError, never>;
};
session: {
create: (input: {
readonly id: string;
readonly userId: string;
readonly expiresAt: string;
}) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
}, DBCallbackFailure | DatabaseError, never>;
getById: (input: string) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
} | undefined, DBCallbackFailure | DatabaseError, never>;
sessionWithUser: (sessionId: string) => Effect<{
session: {
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
};
user: {
readonly id: string;
readonly url?: string | null | undefined;
readonly name: string;
readonly email?: string | null | undefined;
readonly avatar?: string | null | undefined;
readonly username: string;
readonly password?: string | null | undefined;
readonly updatedAt: Date;
readonly createdAt: Date;
readonly emailVerified: boolean;
readonly notifications?: string | null | undefined;
};
} | undefined, DBCallbackFailure | DatabaseError, never>;
delete: (input: string) => AuthDeletionResponse;
update: (input: {
readonly id: string;
readonly newDate: Date;
}) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
}, DBCallbackFailure | DatabaseError, never>;
};
user: {
create: (userData: {
readonly id: string;
readonly url?: string | null | undefined;
readonly name: string;
readonly email?: string | null | undefined;
readonly avatar?: string | null | undefined;
readonly username: string;
readonly password?: string | null | undefined;
readonly updatedAt: string;
readonly createdAt: string | undefined;
readonly emailVerified: boolean;
readonly notifications?: string | null | undefined;
}, rank: "owner" | "admin" | "editor" | "visitor" | "unknown") => Effect<{
readonly id: string;
readonly ...

VanillaJS Version of the SDKCore. Most internal functions will still contain Effects, you can use runSDK from the 'studiocms:sdk' to run these as normal async functions

@example

import { SDKCoreJs, runSDK } from 'studiocms:sdk';
const pages = await runSDK(SDKCoreJs.GET.pages());

SDKCoreJs
,
const runSDK: <A, E>(effect: Effect<A, E, never>) => Promise<A>

Utility function for running components of the SDKCoreJs

@example

import { SDKCoreJs, runSDK } from 'studiocms:sdk';
const pages = await runSDK(SDKCoreJs.GET.pages());

runSDK
} from 'studiocms:sdk';
import
const config: {
route: string;
}
config
from 'myplugin:config';
const
const makeRoute: (slug: string) => string
makeRoute
= (
slug: string
slug
: string) => {
return `/${
const config: {
route: string;
}
config
.
route: string
route
}/${
slug: string
slug
}`;
}
// 'my-plugin' here is used as the identifier for
// the pageType from the plugin definition
const
const pages: CombinedPageData[]
pages
= await
runSDK<CombinedPageData[], DBCallbackFailure | DatabaseError | ParseError | FolderTreeError | CollectorError | PaginateError>(effect: Effect<CombinedPageData[], DBCallbackFailure | DatabaseError | ParseError | FolderTreeError | CollectorError | PaginateError, never>): Promise<CombinedPageData[]>

Alias for runEffect, used to run SDK effects and convert them to plain JavaScript objects.

@parameffect - The Effect to be converted.

@returnsA promise that resolves to the plain JavaScript object representation of the effect's result.

runSDK
(
const SDKCoreJs: {
AUTH: {
verifyEmail: {
get: (input: string) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
readonly token: string;
} | undefined, DBCallbackFailure | DatabaseError, never>;
create: (userId: string) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
readonly token: string;
}, DBCallbackFailure | DatabaseError | GeneratorError, never>;
delete: (input: string) => Effect<DeleteResult, DBCallbackFailure | DatabaseError, never>;
};
oAuth: {
create: (input: {
readonly providerUserId: string;
readonly provider: string;
readonly userId: string;
}) => Effect<{
readonly providerUserId: string;
readonly provider: string;
readonly userId: string;
}, DBCallbackFailure | DatabaseError, never>;
delete: (input: {
readonly userId: string;
readonly provider: string;
}) => AuthDeletionResponse;
searchByProviderId: (input: {
readonly providerUserId: string;
readonly userId: string;
}) => Effect<{
readonly providerUserId: string;
readonly provider: string;
readonly userId: string;
} | undefined, DBCallbackFailure | DatabaseError, never>;
searchProvidersForId: (input: {
readonly userId: string;
readonly providerId: string;
}) => Effect<{
readonly providerUserId: string;
readonly provider: string;
readonly userId: string;
} | undefined, DBCallbackFailure | DatabaseError, never>;
};
permission: {
currentStatus: (input: string) => Effect<{
readonly user: string;
readonly rank: "owner" | "admin" | "editor" | "visitor" | "unknown";
} | undefined, DBCallbackFailure | DatabaseError, never>;
};
session: {
create: (input: {
readonly id: string;
readonly userId: string;
readonly expiresAt: string;
}) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
}, DBCallbackFailure | DatabaseError, never>;
getById: (input: string) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
} | undefined, DBCallbackFailure | DatabaseError, never>;
sessionWithUser: (sessionId: string) => Effect<{
session: {
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
};
user: {
readonly id: string;
readonly url?: string | null | undefined;
readonly name: string;
readonly email?: string | null | undefined;
readonly avatar?: string | null | undefined;
readonly username: string;
readonly password?: string | null | undefined;
readonly updatedAt: Date;
readonly createdAt: Date;
readonly emailVerified: boolean;
readonly notifications?: string | null | undefined;
};
} | undefined, DBCallbackFailure | DatabaseError, never>;
delete: (input: string) => AuthDeletionResponse;
update: (input: {
readonly id: string;
readonly newDate: Date;
}) => Effect<{
readonly id: string;
readonly userId: string;
readonly expiresAt: Date;
}, DBCallbackFailure | DatabaseError, never>;
};
user: {
create: (userData: {
readonly id: string;
readonly url?: string | null | undefined;
readonly name: string;
readonly email?: string | null | undefined;
readonly avatar?: string | null | undefined;
readonly username: string;
readonly password?: string | null | undefined;
readonly updatedAt: string;
readonly createdAt: string | undefined;
readonly emailVerified: boolean;
readonly notifications?: string | null | undefined;
}, rank: "owner" | "admin" | "editor" | "visitor" | "unknown") => Effect<{
readonly id: string;
readonly ...

VanillaJS Version of the SDKCore. Most internal functions will still contain Effects, you can use runSDK from the 'studiocms:sdk' to run these as normal async functions

@example

import { SDKCoreJs, runSDK } from 'studiocms:sdk';
const pages = await runSDK(SDKCoreJs.GET.pages());

SDKCoreJs
.
type GET: {
permissionsLists: {
owners: () => Effect<SingleRank[], UsersError | DBCallbackFailure | DatabaseError, never>;
admins: () => Effect<SingleRank[], UsersError | DBCallbackFailure | DatabaseError, never>;
editors: () => Effect<SingleRank[], UsersError | DBCallbackFailure | DatabaseError, never>;
visitors: () => Effect<SingleRank[], UsersError | DBCallbackFailure | DatabaseError, never>;
all: () => Effect<CombinedRank[], DBCallbackFailure | DatabaseError | UsersError, never>;
};
... 12 more ...;
tags: {
getAll: () => Effect<readonly {
readonly id: number;
readonly name: string;
readonly description: string;
readonly slug: string;
readonly meta: {
readonly [x: string]: unknown;
};
}[], DBCallbackFailure | DatabaseError, never>;
byId: (tagId: number) => Effect<{
readonly id: number;
readonly name: string;
readonly description: string;
readonly slug: string;
readonly meta: {
readonly [x: string]: unknown;
};
} | undefined, DBCallbackFailure | DatabaseError, never>;
bySlug: (slug: string) => Effect<{
readonly id: number;
readonly name: string;
readonly description: string;
readonly slug: string;
readonly meta: {
readonly [x: string]: unknown;
};
} | undefined, DBCallbackFailure | DatabaseError, never>;
};
}
GET
.
packagePages: (packageName: string) => Effect<CombinedPageData[], ParseError | DBCallbackFailure | DatabaseError | FolderTreeError | CollectorError | PaginateError, never> (+1 overload)
packagePages
('my-plugin'));
const {
const slug: string | undefined
slug
} =
const Astro: AstroGlobal<Record<string, any>, AstroComponentFactory, Record<string, string | undefined>>
Astro
.
AstroSharedContext<Record<string, any>, Record<string, string | undefined>>.params: Record<string, string | undefined>

An object containing the values of dynamic route segments matched for a request.

In static builds, this will be the params returned by getStaticPaths(). With on-demand rendering, params can be any value matching the path segments in the dynamic route pattern.

Example

import type { APIContext } from "astro"
export function getStaticPaths() {
return [
{ params: { id: '0' }, props: { name: 'Sarah' } },
{ params: { id: '1' }, props: { name: 'Chris' } },
{ params: { id: '2' }, props: { name: 'Fuzzy' } },
];
}
export function GET({ params }: APIContext): Response {
return new Response(`The current id is ${params.id}.`);
}

Astro reference

params
;
const
const page: CombinedPageData | undefined
page
=
const pages: CombinedPageData[]
pages
.
Array<CombinedPageData>.find(predicate: (value: CombinedPageData, index: number, obj: CombinedPageData[]) => unknown, thisArg?: any): CombinedPageData | undefined (+1 overload)

Returns the value of the first element in the array where predicate is true, and undefined otherwise.

@parampredicate find calls predicate once for each element of the array, in ascending order, until it finds one where predicate returns true. If such an element is found, find immediately returns that element value. Otherwise, find returns undefined.

@paramthisArg If provided, it will be used as the this value for each invocation of predicate. If it is not provided, undefined is used instead.

find
((
page: CombinedPageData
page
) =>
page: CombinedPageData
page
.
slug: string
slug
===
const slug: string | undefined
slug
|| '');
Template
{
slug && page ? (
<div>
<h1>{page.title}</h1>
<StudioCMSRenderer content={page.defaultContent?.content || ''} />
</div>
) : (
<div>
<h1>My Plugin</h1>
<ul>
{pages.length > 0 && pages.map((page) => (
<li>
<a href={makeRoute(page.slug)}>{page.title}</a>
</li>
))}
</ul>
</div>
)
}

The above example defines a dynamic route^ for the plugin that displays a list of blog posts when no slug is provided and displays the content of a blog post when a slug is provided.

In the src/dashboard-grid-items/MyPluginGridItem.astro file, you will define the grid item for the plugin. The following is an example of how to define a grid item for the plugin:

MyPluginGridItem.astro
---
import { SDKCoreJs, runSDK } from 'studiocms:sdk';
// 'my-plugin' here is used as the identifier for
// the pageType from the plugin definition
const pages = await runSDK(SDKCoreJs.GET.packagePages('my-plugin'));
// Get the 5 most recently updated pages from the last 30 days
const recentlyUpdatedPages = pages
.filter((page) => {
const now = new Date();
const thirtyDaysAgo = new Date(now.setDate(now.getDate() - 30));
return new Date(page.updatedAt) > thirtyDaysAgo;
})
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
.slice(0, 5);
---
<div>
<h2>Recently Updated Pages</h2>
<ul>
{recentlyUpdatedPages.length > 0 && recentlyUpdatedPages.map((page) => (
<li>
<a href={Astro.locals.routeMap.mainLinks.contentManagementEdit + `?edit=${page.id}`}>{page.title}</a>
</li>
))}
</ul>
</div>

The above example defines a grid item for the plugin that displays the 5 most recently updated pages from the last 30 days. The grid item includes a list of links to the content management edit page for each page.

Section titled “Integrating with the FrontendNavigationLinks helpers”

If you are looking to use the built-in StudioCMS navigation helpers in your project, similar to the way the @studiocms/blog plugin does, you can create a custom Navigation.astro component:

Navigation.astro
---
import { frontendNavigation } from 'studiocms:plugin-helpers';
// Define the props for the Navigation component
interface Props {
topLevelLinkCount?: number;
};
// Get the top level link count from the props
const { topLevelLinkCount = 3 } = Astro.props;
// Get the site config and page list
const config = Astro.locals.siteConfig.data;
// Get the site title from the config
const { title } = config || { title: 'StudioCMS' };
// Get the main site URL
const {
mainLinks: { baseSiteURL },
} = Astro.locals.routeMap;
// Define the link props for the navigation
type LinkProps = {
text: string;
href: string;
};
// Define the links for the navigation
const links: LinkProps[] = await frontendNavigation();
---
{/* If no dropdown items */}
{ ( links.length < topLevelLinkCount || links.length === topLevelLinkCount ) && (
<div class="navigation">
<div class="title"><a href={baseSiteURL}>{title}</a></div>
<div class="mini-nav">
<button>Menu</button>
<div class="mini-nav-content">
{
links.map(({ text, href }) => (
<a {href}>{text}</a>
))
}
</div>
</div>
{
links.map(({ text, href }) => (
<a class="links" {href}>{text}</a>
))
}
</div>
) }
{/* If dropdown items */}
{ links.length > topLevelLinkCount && (
<div class="navigation">
<div class="title"><a href={baseSiteURL}>{title}</a></div>
<div class="mini-nav">
<button>Menu</button>
<div class="mini-nav-content">
{
links.map(({ text, href }) => (
<a {href}>{text}</a>
))
}
</div>
</div>
{
links.slice(0, topLevelLinkCount).map(({ text, href }) => (
<a class="links" {href}>{text}</a>
))
}
<div class="dropdown">
<button>More ▼</button>
<div class="dropdown-content">
{ links.slice(topLevelLinkCount).map(({ text, href }) => (
<a {href}>{text}</a>
)) }
</div>
</div>
</div>
) }

The above example defines a custom Navigation.astro component that uses the built-in StudioCMS navigation helpers to create a navigation menu for the project. The component includes links to the main site URL, the index page, and any other pages that are set to show on the navigation.

All you need to do is add some styles, and you have a fully functional navigation menu that works with the built-in StudioCMS navigation helpers.