Porte un plugin de WordPress separando su comportamiento de contenido, datos almacenados, rutas HTTP e interfaz de usuario. Luego elija el formato de plugin de EmDash que admita esos requisitos.
Comprobar si el plugin encaja en EmDash
Buenos candidatos implementan comportamiento independiente del núcleo de WordPress, como validación de contenido, llamadas a APIs externas, procesamiento en segundo plano, registros personalizados, ajustes o una herramienta de administración.
No porte un plugin cuyo único propósito sea implementar una función de WordPress que Astro o EmDash ya reemplazan. Ejemplos: caché de páginas PHP, reglas de reescritura de WordPress, selección de plantillas del tema o modificaciones a globales del núcleo.
Si un plugin solo define un tipo de entrada o campos personalizados con poco comportamiento en tiempo de ejecución, cree una colección y un archivo seed de EmDash en lugar de un plugin.
Elegir sandboxed o native
Empiece con Elegir un formato de plugin. Ambos formatos comparten nombres de hooks y las APIs de PluginContext, pero sus paquetes fuente son distintos.
| Requisito | Sandboxed | Native |
|---|---|---|
| Instalación desde el registry | Sí | No |
| Runtime aislado | Sí, con un runner configurado | No |
| Hooks, rutas, KV, almacenamiento estructurado | Sí | Sí |
| Páginas de admin con Block Kit | Sí | Sí |
| Componentes React de admin personalizados | No | Sí |
| Componentes Astro para renderizado público | No | Sí |
| Fragmentos de página sin procesar | No | Sí |
Elija native solo cuando el port necesite una superficie de compilación o interfaz exclusiva de native.
Formato de paquete sandboxed
emdash-plugin init crea el formato sandboxed actual:
my-plugin/
├── emdash-plugin.jsonc
├── src/
│ └── plugin.ts
├── tests/
│ └── plugin.test.ts
├── package.json
└── tsconfig.json
El manifiesto contiene identidad, publisher, capabilities, hosts permitidos y declaraciones de almacenamiento. La versión suele venir de package.json.
El siguiente manifiesto declara una colección de almacenamiento indexada y la capability requerida por content:afterSave:
{
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
"slug": "read-time",
"publisher": "did:plc:abc123def456",
"license": "MIT",
"author": { "name": "Example Author" },
"security": { "email": "security@example.com" },
"capabilities": ["content:read"],
"allowedHosts": [],
"storage": {
"calculations": { "indexes": ["contentId", "updatedAt"] }
}
}
src/plugin.ts exporta por defecto un objeto plano tipado con SandboxedPlugin. Los handlers de hooks sandboxed usan { handler }; los handlers de rutas sandboxed reciben (routeCtx, ctx):
import type { SandboxedPlugin } from "emdash/plugin";
export default {
hooks: {
"content:afterSave": {
handler: async (event, ctx) => {
await ctx.storage.calculations.put(event.content.id, {
contentId: event.content.id,
updatedAt: new Date().toISOString(),
});
},
},
},
routes: {
recent: {
handler: async (_routeCtx, ctx) => {
const result = await ctx.storage.calculations.query({
orderBy: { updatedAt: "desc" },
limit: 10,
});
return { items: result.items };
},
},
},
} satisfies SandboxedPlugin;
La ruta está disponible en /_emdash/api/plugins/read-time/recent. Un campo de almacenamiento debe declararse como índice antes de que una consulta pueda filtrar u ordenar por él.
Compile el paquete con emdash-plugin build; no añada un descriptor src/index.ts escrito a mano a este formato. Lea Su primer plugin sandboxed para el package.json generado, la salida de build y el registro en el sitio.
Formato de paquete native
Un paquete native exporta una factory de descriptor para astro.config.mjs y una factory de runtime construida con definePlugin(). Los entrypoints opcionales de admin y Astro son exportaciones separadas del paquete.
my-native-plugin/
├── src/
│ ├── index.ts
│ ├── admin.tsx
│ └── astro/
│ └── index.ts
├── package.json
└── tsconfig.json
El siguiente entrypoint native reducido muestra las dos piezas requeridas:
import { definePlugin } from "emdash";
import type { PluginDescriptor } from "emdash";
export interface ReadTimeOptions {
wordsPerMinute?: number;
}
export function readTimePlugin(options: ReadTimeOptions = {}): PluginDescriptor {
return {
id: "read-time",
version: "0.1.0",
format: "native",
entrypoint: "@example/plugin-read-time",
capabilities: ["content:read"],
options,
};
}
export function createPlugin(options: ReadTimeOptions = {}) {
return definePlugin({
id: "read-time",
version: "0.1.0",
capabilities: ["content:read"],
admin: {
settingsSchema: {
wordsPerMinute: {
type: "number",
label: "Words per minute",
default: options.wordsPerMinute ?? 200,
min: 1,
},
},
},
hooks: {
"content:afterSave": async (event, ctx) => {
ctx.log.info("Content saved", { id: event.content.id });
},
},
});
}
export default createPlugin;
Los handlers de hooks native pueden ser funciones directamente. Los handlers de rutas native reciben un único argumento de contexto combinado. Mantenga alineadas las copias de descriptor y runtime de id, version, capabilities y entrypoints.
Lea Su primer plugin native antes de añadir páginas React de admin, renderizadores Portable Text o fragmentos de página.
Mapear el comportamiento de WordPress
Hooks
Mapee la intención de una action o filter de WordPress, no solo el nombre:
| WordPress | EmDash |
|---|---|
register_activation_hook() | plugin:install en la primera instalación, o plugin:activate al habilitar |
register_uninstall_hook() | plugin:uninstall |
wp_insert_post_data | content:beforeSave |
save_post | content:afterSave |
before_delete_post | content:beforeDelete |
deleted_post | content:afterDelete |
wp_handle_upload_prefilter | media:beforeUpload |
add_attachment | media:afterUpload |
Los eventos de hooks tienen formas tipadas propias. Consulte la referencia de hooks antes de traducir argumentos de callbacks de WordPress.
Los hooks de contenido que reciben datos de entradas requieren content:read. Añada capabilities según las APIs que llame el port:
| Capability | API disponible |
|---|---|
content:read | Leer contenido y registrar hooks de contenido que exponen datos de entradas |
content:write | Crear, actualizar, publicar o eliminar contenido; también implica lectura |
media:read | Leer registros de medios |
media:write | Crear o actualizar medios; también implica lectura |
network:request | Usar ctx.http para los hosts listados en allowedHosts |
Opciones y tablas personalizadas
Use ctx.settings para la configuración del usuario y ctx.kv para valores internos pequeños. Ambos almacenes están aislados por plugin. Declare credenciales como campos secret en admin.settingsSchema para que EmDash las cifre.
Use colecciones ctx.storage.<collection> declaradas para registros consultables del plugin. La declaración de almacenamiento va en emdash-plugin.jsonc para sandboxed y en definePlugin() para native. No abra la base de datos de EmDash ni interpole SQL desde código del plugin.
La siguiente comparación porta un valor de opción sin exponer globales de WordPress al nuevo plugin:
WordPress
$api_key = get_option('read_time_api_key', '');
update_option('read_time_api_key', $new_api_key); EmDash
import type { PluginContext } from "emdash/plugin";
export async function saveApiKey(ctx: PluginContext, newApiKey: string) {
await ctx.settings.set("apiKey", newApiKey);
}
export async function readApiKey(ctx: PluginContext) {
return await ctx.settings.get<string>("apiKey") ?? "";
} Para una tabla personalizada de WordPress, identifique los campos usados para filtrar y ordenar antes de declarar almacenamiento. El siguiente fragmento de manifiesto sandboxed indexa ambos campos usados por la consulta:
"storage": {
"jobs": { "indexes": ["status", "createdAt"] }
}
El runtime puede entonces almacenar y consultar registros de trabajos:
await ctx.storage.jobs.put("job-123", {
status: "pending",
createdAt: new Date().toISOString(),
});
const pending = await ctx.storage.jobs.query({
where: { status: "pending" },
orderBy: { createdAt: "asc" },
limit: 50,
});
Declare status y createdAt como índices en el manifiesto o en la definición de almacenamiento native antes de ejecutar esa consulta.
Endpoints REST
Mapee una ruta REST de WordPress a una ruta del plugin. EmDash la monta en /_emdash/api/plugins/<plugin-id>/<route-name>. Defina un inputSchema cuando la ruta acepte entrada y devuelva datos serializables a JSON.
Ajustes y páginas de admin
Los plugins sandboxed describen páginas de admin con Block Kit y leen o escriben valores mediante rutas y KV. No envían React a la aplicación de admin.
Los plugins native pueden usar admin.settingsSchema para un formulario generado. Use una exportación adminEntry del paquete para páginas React personalizadas, widgets, widgets de campo o columnas de listas.
Archivos y medios
Use las APIs de medios para archivos subidos o generados. Los plugins sandboxed no tienen acceso al sistema de archivos. Los plugins native comparten el proceso del host, pero escribir archivos locales del deployment no es una estrategia de almacenamiento portable.
Portar el plugin
-
Inventarie hooks, opciones, tablas personalizadas, cron jobs, rutas REST, páginas de admin, bloques, shortcodes y hosts externos de WordPress.
-
Elimine comportamiento que pertenezca al enrutamiento de Astro, al modelo de contenido de EmDash o a la plataforma de deployment.
-
Elija el formato de paquete sandboxed o native. Registre cada capability y host permitido que necesite el comportamiento restante.
-
Defina claves KV y colecciones de almacenamiento estructurado. Añada índices para cada campo usado en
whereuorderBy. -
Porte un comportamiento observable a la vez. Pruebe el hook o la ruta con contenido representativo y casos de error.
-
Añada Block Kit o UI de admin native solo después de que funcionen las rutas y el almacenamiento subyacentes.
-
Pruebe instalación, actualización, activación, desactivación, desinstalación con y sin borrado de datos, y cambios de capabilities.
Próximos pasos
- Manifiesto de plugin sandboxed para el contrato de confianza y metadatos del paquete.
- Capabilities para acceso a contenido, medios, hosts de red y hooks.
- Storage para KV y colecciones indexadas.
- Páginas de admin React para UI exclusiva de native.