Dashboard Widget Marketplace
Publishing dashboard widgets to marketplace.sveltycms.com β package format, install path, licensing models, and update flow.
On this page
Dashboard widgets are first-class marketplace extensions alongside content widgets, plugins, and themes. This guide covers packaging, publishing, installing, and monetizing them.
πͺ Portable Packages
A dashboard widget package is the whole folder: component, widget.json manifest, optional .mdx docs, and optional tests. Uploading a package to marketplace.sveltycms.com distributes the folder as-is; installing writes it back unchanged.
Required package contents
| File | Required | Purpose |
|---|---|---|
<component>.svelte |
β | Widget component (with widgetMeta). |
widget.json |
β | Manifest β id, name, license, size, β¦ |
<component>.mdx |
β | Marketplace description β features, licensing, data source, metadata. Used for the package listing and detail page. |
tests/ |
optional | Unit tests run in CI for your package. |
Manifest
{
"id": "logs",
"name": "System Logs",
"description": "Recent system activity with filtering and search",
"icon": "mdi:text-box-outline",
"version": "1.0.0",
"type": "dashboard-widget",
"author": "SveltyCMS",
"license": "freemium",
"price": 6.99,
"component": "logs-widget",
"defaultSize": { "w": 2, "h": 2 },
"category": "logs"
}
type is always "dashboard-widget". The marketplace catalog (GET /api/packages?type=dashboard-widget) and the in-app catalog (GET /api/marketplace?type=dashboard) use it for filtering.
π₯ Install Path
Marketplace installs target src/routes/(app)/dashboard/widgets/<folder>/ (the packageβs installPath). Discovery is file-based (import.meta.glob), so no registration step is needed β restart the dev server and the widget appears in the picker.
Client-side install
From the browser or admin UI, installations call the marketplace install endpoint:
import { fetchApi } from "@utils/api";
const res = await fetchApi("/api/marketplace/install", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ itemId: "request-rate", type: "dashboard" }),
});
Server-side install
In server scripts or migrations, use the server-only install service:
import { installDashboardWidget } from "@src/services/intelligence/marketplace-install.server";
const widget = await installDashboardWidget("request-rate");
// β writes src/routes/(app)/dashboard/widgets/request-rate/β¦
// (component + widget.json + docs)
Packaging & Extracting for Marketplace
To export a dashboard widget into an isolated, marketplace-ready package folder:
bun run extension:extract dashboard request-rate [outputDir]
To verify that all local extensions match the marketplace catalog spec:
bun run lint:marketplace
Uninstall
Remove the package folder. The next build drops it from the registry; saved layouts referencing it render the built-in βwidget unavailableβ error card until removed.
π° Monetization & Licensing Models
| Tier | Payment | Trial | Description |
|---|---|---|---|
| Free | None | N/A | Available to all installations indefinitely. |
| Hybrid (Freemium) | Paid | 14-Day | Basic view is free; advanced features require an active license. |
| Fully Paid | Paid | 14-Day | The entire widget is locked behind a license gate. |
- 14-day key-less trial β calculated from the first registered adminβs creation timestamp;
checkExtensionLicensereturnsactive: trueduring the window. - Demo keys β superadmins can provision
SLM-DEMO-keys extending access by 14 days.
Gate the UI
<script lang="ts">
import { checkExtensionLicense } from "@src/utils/license-manager";
import UpgradePrompt from '@components/ui/upgrade-prompt.svelte';
let status = $state({ active: true, hasLicense: false });
$effect(() => {
checkExtensionLicense("dashboard", "logs").then((s) => (status = s));
});
</script>
{#if status.active}
<!-- widget content -->
{:else}
<UpgradePrompt extensionId="dashboard:logs" price="β¬6.99" />
{/if}
Gate the data (server-side)
The backing API endpoint should also call checkExtensionLicense and return 403 Forbidden when the check fails, so premium data is never served to unlicensed clients.
π Updates
Installed packages appear in marketplace.checkUpdates([{ id, version }]) results when a newer version is published. The update flow replaces the package folder contents; the component filename stays stable so user layouts survive upgrades.
β Submission Standards
- Folder kebab-case = package id; entry component is
index.svelte; docs arereadme.mdx. widget.jsonpresent and valid; matcheswidgetMetain the component.<component>.mdxrequired β marketplace description with features, licensing model, data source endpoint(s), and widget metadata.- WCAG 2.2 AA (keyboard navigation, ARIA, contrast, focus states).
- No hardcoded secrets; no
Math.random()in security-sensitive paths. - Optional
tests/folder.
Core commerce packages shipped with SveltyCMS: Commerce Orders (dashboard:commerce-orders, β¬12.99) and Commerce Inventory (dashboard:commerce-inventory, β¬12.99). Both are freemium (14-day key-less trial, then 403 LICENSE_REQUIRED). They read the optional ecommerce preset collections and render an empty state when those collections are not installed.