Build for Dunea
Manifest reference
Every field of a plugin, theme, template or skill manifest, with the limits the marketplace enforces.
A manifest is a JSON object that describes your listing. The marketplace validates every submitted manifest with the schemas exported by @dunea/plugin-sdk (pluginManifestSchema, themeManifestSchema, templateManifestSchema, skillManifestSchema), and validateManifest() runs exactly the same checks locally.
Keep the manifest in dunea.plugin.json (or dunea.theme.json, dunea.template.json, dunea.skill.json) at the root of your project. The dunea-plugin CLI looks for those names, and also accepts a .js or .cjs file that exports the manifest.
{
"schemaVersion": 1,
"kind": "plugin",
"id": "order-alerts",
"version": "1.0.0",
"...": "the rest of the fields below"
}If you prefer TypeScript, the typed helpers definePlugin(), defineTheme(), defineTemplate() and defineSkill() fill in schemaVersion and kind and give you autocomplete. The CLI does not read .ts files, so compile the manifest to .js, or write it out as JSON:
import { writeFileSync } from 'node:fs';
import { definePlugin } from '@dunea/plugin-sdk';
const manifest = definePlugin({
id: 'order-alerts',
version: '1.0.0',
// ...the rest of the fields below
});
writeFileSync('dunea.plugin.json', `${JSON.stringify(manifest, null, 2)}\n`);Shared fields
Every kind of listing has these fields.
| Field | Type | Required | Constraints |
|---|---|---|---|
schemaVersion | number | Yes | Must be 1 (MANIFEST_SCHEMA_VERSION). Set for you by definePlugin / defineTheme. |
kind | string | Yes | "plugin", "theme", "template" or "skill". Set for you by the helpers. |
id | string | Yes | Lowercase letters, digits and dashes, starting with a letter, 3 to 41 characters (^[a-z][a-z0-9-]{2,40}$). |
version | string | Yes | Semver x.y.z, digits only (1.4.0). No pre-release or build suffixes. |
name | string | Yes | 2 to 60 characters. |
tagline | string | Yes | 10 to 120 characters. One line shown on listing cards. |
description | string | Yes | 40 to 4,000 characters, plain text. Shown on the listing page; blank lines start a new paragraph. |
icon | string | Yes | An https:// URL, at most 500 characters. |
screenshots | string[] | No | Up to 8 https:// URLs. Defaults to []. |
developer | object | Yes | See developer. |
pricing | object | Yes | See pricing. |
categories | string[] | Yes | 1 to 3 values. The allowed list differs by kind, see below. |
Every URL field must start with https:// and be at most 500 characters.
developer
| Field | Type | Required | Constraints |
|---|---|---|---|
name | string | Yes | 2 to 80 characters. |
website | string | No | https:// URL. |
supportEmail | string | Yes | A valid email address. Shown to owners on your listing. |
privacyUrl | string | Yes | https:// URL of your privacy policy. |
pricing
pricing is one of three shapes, chosen by model. Amounts are in US cents.
{ "model": "free" }{ "model": "one_time", "amount": 1900, "currency": "USD" }{ "model": "monthly", "amount": 900, "currency": "USD", "trialDays": 14 }| Field | Applies to | Constraints |
|---|---|---|
model | all | "free", "one_time" or "monthly". |
amount | one_time, monthly | Integer, 100 to 99,900 (that is, $1 to $999). |
currency | one_time, monthly | Must be "USD". |
trialDays | monthly | Optional integer, 0 to 30. |
Templates are free or one_time only. See Pricing and payouts for how paid listings are billed.
Plugin fields
| Field | Type | Required | Constraints |
|---|---|---|---|
kind | string | Yes | "plugin" |
categories | string[] | Yes | 1 to 3 of: marketing, sales, shipping, payments, analytics, customer-support, content, design, productivity, ai, integrations. |
permissions | string[] | Yes | Permission IDs from the permissions table. Can be empty. |
subscribes | string[] | No | Event IDs from the events table. Defaults to []. |
webhookUrl | string | Conditional | https:// URL where Dunea sends signed events and tool calls. Required if subscribes or agentTools is not empty. |
panel | object | No | { url, title }: url is an https:// URL, title is 2 to 40 characters. See Workbench panels. |
agentTools | object[] | No | Up to 10 tools. Defaults to []. See agentTools. |
usage | object | No | Metered usage your service reports. See usage. |
siteScripts | object[] | No | Up to 3 scripts. Defaults to []. See siteScripts. |
settings | object[] | No | Up to 20 fields. Defaults to []. See settings. |
agentTools
Each entry is a tool the Dunea AI can call. See AI tools.
| Field | Type | Constraints |
|---|---|---|
name | string | Lowercase letters, digits and underscores, starting with a letter, 3 to 41 characters (^[a-z][a-z0-9_]{2,40}$). Unique within the manifest. |
description | string | 20 to 600 characters. |
input | object | See below. |
price | object | Optional. { credits }: what each successful call costs the owner, 0.05 to 50 credits with at most two decimals. Omit for a free tool. See Priced tools and metered usage. |
input describes the tool's arguments:
| Field | Type | Constraints |
|---|---|---|
input.type | string | Must be "object". |
input.properties | object | Keys match ^[a-zA-Z][a-zA-Z0-9_]{0,40}$. Each value is { type, description, enum? }. |
input.properties.*.type | string | "string", "number", "integer" or "boolean". Nested objects and arrays are not supported. |
input.properties.*.description | string | At most 300 characters. |
input.properties.*.enum | string[] | Optional. Up to 30 values, each at most 80 characters. |
input.required | string[] | Optional. Up to 20 names, each of which must be a key in properties. |
usage
Optional. Declares metered usage your service reports with POST /usage, charged to the owner's credits within their monthly cap.
| Field | Type | Constraints |
|---|---|---|
unit | string | 2 to 40 characters, e.g. "label". |
maxCreditsPerUnit | number | The most you may charge per unit: 0.05 to 50 credits, at most two decimals. |
description | string | 20 to 300 characters. Shown to the owner at install. |
suggestedMonthlyCapCredits | number | 1 to 10,000. Defaults to 50. The cap Dunea suggests at install. |
siteScripts
| Field | Type | Constraints |
|---|---|---|
src | string | https:// URL of the script. |
consent | string | "necessary", "analytics" or "marketing". |
placement | string | "head" or "body". Defaults to "body". |
settings
Fields the owner fills in when they install your plugin. Dunea renders the form and sends you the values.
| Field | Type | Constraints |
|---|---|---|
key | string | Starts with a letter, then letters, digits or underscores, at most 41 characters (^[a-zA-Z][a-zA-Z0-9_]{0,40}$). |
label | string | 1 to 80 characters. |
type | string | "text", "secret", "select", "boolean", "number", "email" or "url". |
help | string | Optional, at most 300 characters. |
placeholder | string | Optional, at most 120 characters. |
required | boolean | Optional. |
options | object[] | Optional. Up to 50 { value, label } pairs, each at most 80 characters. Use with select. |
Values arrive as settings in the plugin.installed event, and you can read the current values any time with GET /settings. Owners can change them later; subscribe to plugin.settings.updated to hear when they do.
Cross-field rules
The validator also checks these rules across fields:
- Every event in
subscribesneeds the permissions listed for it in the events table. For example,shop.order.paidneedsorders:read. webhookUrlis required ifsubscribesoragentToolsis not empty.siteScriptsrequires thesite:scriptspermission.- Tool names in
agentToolsmust be unique. - Every name in a tool's
input.requiredmust exist in itsinput.properties.
Theme fields
| Field | Type | Required | Constraints |
|---|---|---|---|
kind | string | Yes | "theme" |
categories | string[] | Yes | 1 to 3 of: minimal, bold, editorial, playful, luxury, tech, retail, portfolio, local-business. |
tokens | object | Yes | See tokens. |
guide | string | Yes | Markdown, 200 to 12,000 characters. Art direction for the Dunea AI. |
sections | object[] | No | Up to 20 { name, recipe }: name is 2 to 60 characters, recipe is markdown of 40 to 3,000 characters. Defaults to []. |
tokens
| Field | Type | Constraints |
|---|---|---|
colors | object | All of background, surface, text, muted, primary, primaryText, accent, border. Each a #rrggbb hex colour. |
darkColors | object | Optional. The same eight keys, for a dark mode. Omit for a single-mode theme. |
fonts.heading | string | A family from the font allow-list. |
fonts.body | string | A family from the font allow-list. |
fonts.mono | string | Optional. A family from the font allow-list. |
radius | number | Corner radius in px, integer 0 to 32. 0 is sharp. |
density | string | "compact", "comfortable" or "airy". |
typeScale | number | Heading scale ratio, 1.1 to 1.6 (for example 1.25). |
See the Themes guide for how to use each one.
Template fields
See Templates for the snapshot rules and the remix flow.
| Field | Type | Required | Constraints |
|---|---|---|---|
kind | string | Yes | "template" |
categories | string[] | Yes | 1 to 3 of: restaurant, retail, salon-beauty, health, education, professional-services, trades, events, portfolio, agency, community, saas. |
sourceProjectId | string | Yes | UUID of the Dunea project to snapshot. It must be yours. |
previewUrl | string | Yes | https:// URL of a live demo. |
pages | string[] | Yes | 1 to 30 page names, each 1 to 40 characters. |
theme | string | No | Slug of a theme listing it's designed with. |
recommendedPlugins | string[] | No | Up to 10 plugin slugs. Defaults to []. Suggested after a remix, never installed automatically. |
firstPrompt | string | No | 10 to 300 characters. What the owner should tell the AI first. |
Skill fields
See Skills for how the AI uses them.
| Field | Type | Required | Constraints |
|---|---|---|---|
kind | string | Yes | "skill" |
categories | string[] | Yes | 1 to 3 of: copywriting, seo, design, ecommerce, marketing, industry, localization, accessibility, conversion. |
instructions | string | Yes | Markdown, 200 to 20,000 characters. What the AI follows. |
triggers | string[] | Yes | 1 to 12 phrases, each 3 to 80 characters. When the AI should apply the skill. |
recommendedThemes | string[] | No | Up to 5 theme slugs. Defaults to []. |
recommendedPlugins | string[] | No | Up to 5 plugin slugs. Defaults to []. |
Validate locally
import { readFileSync } from 'node:fs';
import { validateManifest } from '@dunea/plugin-sdk';
const manifest: unknown = JSON.parse(readFileSync('dunea.plugin.json', 'utf8'));
const result = validateManifest(manifest);
if (!result.ok) {
// e.g. "subscribes: \"shop.order.paid\" needs the \"orders:read\" permission"
console.error(result.errors.join('\n'));
process.exit(1);
}Each error is path: message. dunea-plugin validate runs the same check from the command line, and dunea-plugin check adds the rest of the review checklist (screenshots, descriptions, prices, contrast, skill instructions) with a rule id for each finding.