Porta un plugin WordPress separando comportamento dei contenuti, dati memorizzati, route HTTP e interfaccia utente. Poi scegli il formato plugin EmDash che supporta tali requisiti.
Verificare se il plugin appartiene a EmDash
Buoni candidati gestiscono comportamento indipendente dal core WordPress, come validazione contenuti, chiamate API esterne, elaborazione in background, record personalizzati, impostazioni o uno strumento admin.
Non portare un plugin il cui unico scopo è implementare una funzione WordPress già sostituita da Astro o EmDash. Esempi: cache pagine PHP, regole rewrite WordPress, selezione template del tema o modifiche ai global del core.
Per un plugin che definisce solo un custom post type o campi con poco comportamento runtime, crea invece una collection EmDash e un file seed.
Scegliere sandboxed o native
Inizia da Scegliere un formato plugin. I due formati condividono nomi hook e API PluginContext, ma i pacchetti sorgente differiscono.
| Requisito | Sandboxed | Native |
|---|---|---|
| Installazione dal registry | Sì | No |
| Runtime isolato | Sì, con runner configurato | No |
| Hook, route, KV, storage strutturato | Sì | Sì |
| Pagine admin Block Kit | Sì | Sì |
| Componenti React admin personalizzati | No | Sì |
| Componenti Astro per il rendering pubblico | No | Sì |
| Fragment di pagina grezzi | No | Sì |
Scegli native solo quando il port richiede una superficie build-time o UI esclusiva di native.
Formato pacchetto sandboxed
emdash-plugin init crea il formato sandboxed attuale:
my-plugin/
├── emdash-plugin.jsonc
├── src/
│ └── plugin.ts
├── tests/
│ └── plugin.test.ts
├── package.json
└── tsconfig.json
Il manifest contiene identità, publisher, capabilities, host consentiti e dichiarazioni di storage. La versione proviene normalmente da package.json.
Il manifest seguente dichiara una collection di storage indicizzata e la capability richiesta da 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 esporta per default un plain object tipizzato con SandboxedPlugin. Gli handler hook sandboxed usano { handler }; gli handler route sandboxed ricevono (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 route è disponibile su /_emdash/api/plugins/read-time/recent. Un campo storage deve essere dichiarato come indice prima che una query possa filtrare o ordinare su di esso.
Compila il pacchetto con emdash-plugin build; non aggiungere un descrittore src/index.ts scritto a mano a questo formato. Leggi Il tuo primo plugin sandboxed per il package.json generato, l’output di build e la registrazione sul sito.
Formato pacchetto native
Un pacchetto native esporta sia una factory di descrittore per astro.config.mjs sia una factory runtime costruita con definePlugin(). Gli entrypoint admin e Astro opzionali sono export di pacchetto separati.
my-native-plugin/
├── src/
│ ├── index.ts
│ ├── admin.tsx
│ └── astro/
│ └── index.ts
├── package.json
└── tsconfig.json
Il seguente entrypoint native ridotto mostra i due pezzi richiesti:
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;
Gli handler hook native possono essere funzioni direttamente. Gli handler route native ricevono un unico argomento di contesto combinato. Mantieni allineate le copie descrittore e runtime di id, version, capabilities ed entrypoint.
Leggi Il tuo primo plugin native prima di aggiungere pagine admin React, renderer Portable Text o fragment di pagina.
Mappare il comportamento WordPress
Hook
Mappa l’intento di un’action o filter WordPress, non solo il nome:
| WordPress | EmDash |
|---|---|
register_activation_hook() | plugin:install alla prima installazione, o plugin:activate all’abilitazione |
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 |
Gli eventi hook hanno forme tipizzate proprie. Consulta la reference hook prima di tradurre gli argomenti dei callback WordPress.
Gli hook contenuto che ricevono dati di entry richiedono content:read. Aggiungi capabilities in base alle API chiamate dal port:
| Capability | API resa disponibile |
|---|---|
content:read | Leggere contenuti e registrare hook contenuto che espongono dati entry |
content:write | Creare, aggiornare, pubblicare o eliminare contenuti; implica anche lettura |
media:read | Leggere record media |
media:write | Creare o aggiornare media; implica anche lettura |
network:request | Usare ctx.http per gli host elencati in allowedHosts |
Opzioni e tabelle personalizzate
Usa ctx.settings per la configurazione utente e ctx.kv per piccoli valori interni. Entrambi gli store sono isolati per plugin. Dichiara le credenziali come campi secret in admin.settingsSchema così EmDash le cifra.
Usa collection ctx.storage.<collection> dichiarate per record plugin interrogabili. La dichiarazione storage appartiene a emdash-plugin.jsonc per sandboxed e a definePlugin() per native. Non aprire il database EmDash né interpolare SQL dal codice plugin.
Il confronto seguente porta un valore opzione senza esporre i global WordPress al nuovo 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") ?? "";
} Per una tabella personalizzata WordPress, identifica i campi usati per filtrare e ordinare prima di dichiarare lo storage. Il seguente frammento manifest sandboxed indicizza entrambi i campi usati dalla query:
"storage": {
"jobs": { "indexes": ["status", "createdAt"] }
}
Il runtime può quindi memorizzare e interrogare record job:
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,
});
Dichiara sia status sia createdAt come indici nel manifest o nella definizione storage native prima di eseguire quella query.
Endpoint REST
Mappa una route REST WordPress a una route plugin. EmDash la monta su /_emdash/api/plugins/<plugin-id>/<route-name>. Definisci un inputSchema quando la route accetta input e restituisci dati serializzabili JSON.
Impostazioni e pagine admin
I plugin sandboxed descrivono pagine admin con Block Kit e leggono o scrivono valori tramite route e KV. Non spediscono React nell’applicazione admin.
I plugin native possono usare admin.settingsSchema per un form generato. Usa un export adminEntry del pacchetto per pagine React personalizzate, widget, widget di campo o colonne elenco.
File e media
Usa le API media per file caricati o generati. I plugin sandboxed non hanno accesso al filesystem. I plugin native condividono il processo host, ma scrivere file locali al deployment non è una strategia di storage portabile.
Portare il plugin
-
Inventaria hook, opzioni, tabelle personalizzate, cron job, route REST, pagine admin, blocchi, shortcode e host esterni WordPress.
-
Rimuovi comportamento che appartiene al routing Astro, al modello contenuti EmDash o alla piattaforma di deployment.
-
Scegli il formato pacchetto sandboxed o native. Registra ogni capability e host consentito necessario al comportamento rimanente.
-
Definisci chiavi KV e collection storage strutturato. Aggiungi indici per ogni campo usato in
whereoorderBy. -
Porta un comportamento osservabile alla volta. Testa hook o route con contenuti rappresentativi e casi di errore.
-
Aggiungi Block Kit o UI admin native solo dopo che route e storage sottostanti funzionano.
-
Testa installazione, upgrade, attivazione, disattivazione, disinstallazione con e senza cancellazione dati e cambi di capabilities.
Passi successivi
- Manifest plugin sandboxed per contratto di fiducia e metadati pacchetto.
- Capabilities per accesso a contenuti, media, host di rete e hook.
- Storage per KV e collection indicizzate.
- Pagine admin React per UI esclusiva native.