Creator guide · 1 of 3
Build a VibeFuse widget — from empty folder to marketplace listing
A VibeFuse widget is a tiny web app: a folder with manifest.json and index.html. VibeFuse serves it over the widget:// protocol inside a sandboxed iframe on the canvas, next to your terminals and agents. This guide walks through the whole loop — write the files, run it on your own canvas, publish it from Settings → Marketplace, polish the listing, and price it if you want to sell.
What you are building
By the end you will have a working Quote Board widget — a rotating quote card with a shuffle button and favorites that persist between sessions — installed on your canvas and listed on the marketplace. Swap the content for anything you like; the packaging, testing, and publishing steps are identical for every widget.
- Format
- Folder with
manifest.json+ entry HTML - Runtime
- Sandboxed iframe over
widget:// - Where it lives
C:\vibefuse\widgets- Time to first run
- ~10 minutes
What a marketplace widget looks like on the canvas
Prerequisites
- VibeFuse for Windows installed and signed in with your complimentary
VF-key (Account → Product Keys). - Any text editor. No Node, bundler, or framework is required — a widget is static HTML.
- Basic HTML/CSS/JavaScript. If you can build a web page, you can build a widget.
- To sell (optional): a finished seller profile and Stripe Connect onboarding in Account → Stripe Connect.
Package anatomy
Only two files are mandatory. Everything else is optional and referenced relatively from your HTML.
| Rule | Detail |
|---|---|
| Allowed file types | html, htm, css, js, mjs, json, svg, md, txt, map |
| Size limits | 750 KB per file · ~2.5 MB per package (text only) |
| Binary assets | Not uploaded. Inline SVG, use CSS gradients, data-URIs for tiny images, or load from a URL you host |
| Paths | Relative to the package root. .. and .git/ paths are dropped |
Step 1 — manifest.json
The manifest tells VibeFuse what the package is, how big it should spawn, and what it needs. Copy this and change the id, name, and description.
{
"id": "com.yourname.quote-board",
"name": "Quote Board",
"version": "1.0.0",
"description": "Rotating motivational quotes with a one-click shuffle. Remembers your favorites.",
"author": "Your Name",
"license": "MIT",
"icon": "sparkles",
"category": "utility",
"kind": "marketplace",
"runtime": "iframe",
"entry": "index.html",
"permissions": ["storage"],
"defaultSize": { "w": 4, "h": 5 },
"minSize": { "w": 3, "h": 4 },
"tags": ["quotes", "focus", "productivity"]
}
| Field | Required | What it does |
|---|---|---|
id | Yes | Globally unique, reverse-DNS style. 3–128 chars of letters, numbers, . _ -. Republishing with the same id updates the listing in place — never change it after launch. |
name | Yes | Display name on the rail, catalog cards, and the listing page. |
version | No | Semver string. Defaults to 1.0.0. Bump it every time you republish. |
description | No | One or two sentences. Shown on cards; falls back to name. |
author | No | Overwritten with your profile name when you publish. |
license | No | Free text, e.g. MIT. Shown to buyers. |
icon | No | Icon key for the rail (sparkles, hexagon, clock, terminal …). Defaults to sparkles. |
category | No | One of cli, browser, utility, media, custom. Anything else becomes utility. |
kind / runtime | No | Always normalized to marketplace / iframe on publish. |
entry | No | The HTML file to load. Defaults to index.html; the file must exist in the package. |
permissions | No | Array of capabilities the widget declares. Defaults to ["storage"]. See Permissions. |
defaultSize | No | Grid units { "w", "h" } used when the widget first spawns. Default 4 × 6. |
minSize | No | Smallest size a user can drag it to. Default 3 × 4. |
tags | No | Search keywords. marketplace is added automatically. |
id carefully. It is the identity the marketplace uses to match your future updates to this listing, and an id already claimed by another creator is rejected at publish time.Step 2 — index.html starter
A complete, working widget. It is a normal HTML document: one file, inline styles and script, no dependencies. Notice the dark background — widgets sit on a dark canvas, so design for it.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Quote Board</title>
<style>
* { box-sizing: border-box; }
html, body {
height: 100%; margin: 0;
background: linear-gradient(165deg, #0a1018, #06080d);
color: #eef2f8;
font-family: Inter, system-ui, sans-serif;
display: grid; place-items: center;
}
.card {
width: min(92%, 420px);
padding: 22px 24px;
border-radius: 18px;
border: 1px solid rgba(201, 169, 98, 0.22);
background: rgba(10, 16, 26, 0.72);
text-align: center;
}
.kicker { font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; opacity: 0.5; }
blockquote { margin: 12px 0 6px; font-size: 18px; line-height: 1.4; min-height: 3.2em; }
cite { display: block; font-style: normal; font-size: 12px; color: #c9a962; }
.row { display: flex; gap: 8px; justify-content: center; margin-top: 16px; }
button {
border: 1px solid rgba(201, 169, 98, 0.35);
background: rgba(201, 169, 98, 0.12);
color: inherit; border-radius: 999px; padding: 8px 16px;
font: inherit; font-size: 13px; cursor: pointer;
}
button:hover { background: rgba(201, 169, 98, 0.22); }
button.is-on { background: #c9a962; color: #14110a; }
.count { margin-top: 10px; font-size: 11px; opacity: 0.55; }
</style>
</head>
<body>
<div class="card">
<div class="kicker">Quote board</div>
<blockquote id="q"></blockquote>
<cite id="a"></cite>
<div class="row">
<button id="next">Shuffle</button>
<button id="fav">♥ Favorite</button>
</div>
<div class="count"><span id="n">0</span> favorites saved</div>
</div>
<script>
// Widgets are plain web pages inside a sandboxed iframe.
// Use standard browser APIs — no framework or build step required.
const quotes = [
["Simplicity is the soul of efficiency.", "Austin Freeman"],
["Make it work, make it right, make it fast.", "Kent Beck"],
["Programs must be written for people to read.", "Harold Abelson"],
["The best way to predict the future is to invent it.", "Alan Kay"],
["Deleted code is debugged code.", "Jeff Sickel"]
];
// "storage" permission → localStorage persists between canvas sessions.
const KEY = "quote-board:favorites";
const favorites = new Set(JSON.parse(localStorage.getItem(KEY) || "[]"));
const q = document.getElementById("q");
const a = document.getElementById("a");
const n = document.getElementById("n");
const fav = document.getElementById("fav");
let index = Math.floor(Math.random() * quotes.length);
function render() {
const [text, author] = quotes[index];
q.textContent = "“" + text + "”";
a.textContent = "— " + author;
fav.classList.toggle("is-on", favorites.has(index));
n.textContent = String(favorites.size);
}
document.getElementById("next").onclick = () => {
index = (index + 1 + Math.floor(Math.random() * (quotes.length - 1))) % quotes.length;
render();
};
fav.onclick = () => {
favorites.has(index) ? favorites.delete(index) : favorites.add(index);
localStorage.setItem(KEY, JSON.stringify([...favorites]));
render();
};
render();
</script>
</body>
</html>
Every line above is standard browser code. There is no VibeFuse SDK to import: the host provides the window, the sandbox, and persistence; your page provides the experience.
Live example — the exact code above, running
This is the index.html from Step 2 rendered in a sandboxed iframe, the same way VibeFuse renders it on the canvas. Click Shuffle and Favorite; reload the page and the favorites count survives because the widget uses localStorage.
On the desktop this window has the standard VibeFuse widget chrome: title, fill-view, and close. The iframe content is yours.
Two more shipped widgets you can read as reference: Focus Timer (interval timer with cycles) and Local Time Clock (ticking clock with timezone). Both are under 60 lines.
Step 3 — Saving state between sessions
Declare "storage" in permissions (the default) and use localStorage or IndexedDB. Storage is scoped to your widget origin, so namespacing keys keeps things tidy when you ship a second widget.
const KEY = "quote-board:settings";
// Read once on boot, with a safe fallback.
const settings = Object.assign(
{ theme: "gold", interval: 30 },
JSON.parse(localStorage.getItem(KEY) || "{}")
);
// Write whenever something changes.
function save(patch) {
Object.assign(settings, patch);
localStorage.setItem(KEY, JSON.stringify(settings));
}
save({ interval: 45 });
- Each canvas instance of your widget shares the same origin storage — if you need per-instance state, key it by a random id you generate on first run and keep in
sessionStorage. - Keep payloads small (kilobytes, not megabytes). Widgets boot every time a session opens.
- Never store secrets. Widgets are plain files a user can open in Notepad.
Step 4 — Design for the canvas
Widgets are resized freely by the user and sit beside terminals, browsers, and agent chats. A few habits make yours feel native:
Layout
- Fill the viewport:
html, body { height: 100%; margin: 0 } - Center a single card with
display: grid; place-items: center - Use
min()/clamp()so text scales from 3×4 to full-screen - Respect
minSize— test at the smallest size you allow
Look & feel
- Dark base (
#06080d–#0a1018) with one accent — gold#c9a962and blue#8fb0d4match the shell - Translucent panels:
rgba(10,16,26,.72)+ 1px border at 20% alpha - System font stack (
Inter, system-ui, sans-serif) — no web-font downloads - Pill buttons, 999px radius, hover state only (no heavy animations)
The canvas itself is themed by the user's Appearance settings or an installed styling pack; your widget's interior is fully yours. Neutral dark surfaces look right under every theme.
Widget window chrome VibeFuse draws around your iframe
Permissions & limits
Permissions are declared in the manifest so users and reviewers can see what a widget intends to do. Request the minimum.
| Permission | Use it when |
|---|---|
storage | You persist anything with localStorage / IndexedDB. Default. |
network | You call external HTTP APIs (weather, crypto prices, your own backend). Use HTTPS and handle offline. |
clipboard | You read or write the clipboard (copy buttons). |
webview | You embed third-party pages in an inner iframe. |
voice | You integrate with the voice dock / TTS surface. |
pty, shell, fs.read, fs.write | Reserved for privileged built-in widgets. Marketplace iframes run in a sandbox and do not get shell or file-system access — build those workflows as skills for the agent instead. |
- Widgets cannot reach other widgets, the terminal, or the file system. They are isolated web pages.
- Anything with obfuscated code, crypto-mining, or hidden network calls is removed from the marketplace and the seller account can lose listing rights.
Step 5 — Test it on your own canvas
-
01
Drop the folder into your widgets directory
Copy
quote-board\intoC:\vibefuse\widgets(the path is shown in Settings → Widgets and Settings → Marketplace; use Reveal folder to open it). -
02
Rescan the catalog
Reopen Settings, or restart VibeFuse. The widget appears in Settings → Widgets with a Local badge and under Widgets in the left rail.
-
03
Spawn it
Click it in the rail (or ask Jarvis: “open the quote board widget”). It spawns at your
defaultSize. Resize down tominSizeand up to fill-view to check the layout. -
04
Iterate
Edit
index.html, then close and reopen the widget to reload the iframe. Right-click → Reload also works on most builds. -
05
Check persistence
Favorite a quote, close the widget, reopen — the count should survive. Close the whole session and reopen to confirm it survives restarts too.
Settings → Widgets: built-in + local + marketplace catalog
Widget cards with Local / Marketplace badges
Step 6 — Publish from Settings → Marketplace
Publishing uploads the text files in your folder and creates (or updates) a marketplace listing tied to your account. Your profile name is stamped as author.
-
01
Confirm your license is linked
The top of Settings → Marketplace shows your seller profile when a valid
VF-key is active. If not, grab yours from Account → Product Keys. -
02
Find the widget under Local widgets
Each folder in
C:\vibefuse\widgetsshows as a card with a status: Not on marketplace, Uploaded · unpublished, or Live on marketplace. Use Choose other folder to publish from anywhere else on disk. -
03
Click “List on marketplace”
VibeFuse validates the manifest (id, name, entry file present, allowed file types, size) and uploads. Errors are shown inline — fix the file and click again.
-
04
Publish
New uploads may land as unpublished. Click Publish on the card, or Publish all (N) in the banner. The card flips to Live on marketplace with a View link.
Settings → Marketplace: seller profile, local widgets, listings
Local package cards: Not on marketplace → Live on marketplace
id). Publishing a folder whose id or slug belongs to another creator is rejected.Step 7 — Polish the listing on the web
Uploads carry code; the storefront carries the pitch. Open Account → Marketplace to finish the public page:
| Field | Recommendation |
|---|---|
| Card image | Square-ish, dark background, the widget at its default size. Shown on catalog cards and the trending slider. |
| Banner | Wide (roughly 3:1). The detail page hero fades it under the title — keep the focal point centered. |
| Screenshots + captions | 3–5 shots: default size, resized small, a state that shows value (favorites, settings). One-line captions. |
| Long description | What it does, how to use it, what it stores, what it calls over the network. Rich text is supported. |
| Published / Unpublished | Unpublish hides the page without deleting the package or losing saves. |
Your listing page lives at /marketplace/widget/<slug> and shows version, category, saves, views, a screenshot slider, your creator card, and Save to VibeFuse. Followers of your storefront see new drops on the marketplace pulse.
The in-app marketplace where buyers find your widget
Step 8 — Free or paid
Free ($0.00)
Anyone signed in can Save to VibeFuse; the package syncs into their C:\vibefuse\widgets on next launch. Best for building followers and reputation.
Paid ($0.50 – $500.00)
Requires Stripe Connect fully enabled. Buyers pay through hosted Stripe Checkout; you keep 80%, Fuse Intelligence takes a 20% platform fee at checkout. Earnings show on Account Overview and in Stripe.
- Set the USD price in Account → Marketplace; two decimals, min $0.50, max $500.
- You never invoice buyers yourself — Stripe handles receipts, tax lines, and payouts.
- A paid widget plus a matching skill and styling pack is an easier sale than three separate items.
Step 9 — Ship an update
-
01
Edit the files
Change
index.html, add files, whatever you need. Keep the manifestididentical. -
02
Bump the version
"version": "1.1.0". Buyers see the version on the listing page; it also helps you read support questions. -
03
Update listing
In Settings → Marketplace the card now reads Live on marketplace with an Update listing button. Click it — files and metadata are replaced in place; saves, views, and price are kept.
-
04
Users get it on next sync
Library sync runs at VibeFuse launch and when the in-app marketplace closes, so installs pick up the new package automatically.
Pre-publish checklist
manifest.jsonhas a unique reverse-DNSid, a clearname, and a one-sentencedescriptionentryfile exists and opens correctly when double-clicked in a browser- Only allowed text file types; no file over 750 KB; nothing loaded from
node_modules - Looks right at
minSize,defaultSize, and fill-view - Dark background, no white flash on load, no web-font requests
permissionslists exactly what you use; network calls are HTTPS and documented in the description- No secrets, analytics beacons, or obfuscated code
- Card image, banner, 3+ screenshots with captions, long description written
- Price decided; Stripe Connect enabled if paid
Troubleshooting
| Symptom | Fix |
|---|---|
| Widget does not appear in Settings → Widgets | Folder must be directly inside C:\vibefuse\widgets and contain a valid manifest.json. Check for trailing commas — JSON, not JS. Reopen Settings to rescan. |
| Blank white or black iframe | entry points to a file that is missing or misnamed. Open DevTools on the widget (right-click → Inspect) and read the console. |
| “manifest.json must include id and name” | Both fields are required strings. The id must match ^[a-z0-9][a-z0-9._-]{2,127}$. |
| “Unsupported file type” | Remove images/fonts/binaries from the folder or move them to a URL you host. Only text types upload. |
| “already listed by another creator” | Change your id — someone else owns that one. Use your own namespace: com.yourname.*. |
| Update listing did nothing | Bump version and make sure you published from the same folder / same id. Check the tab's sync log. |
| State resets every time | Confirm "storage" is in permissions and you are writing to localStorage, not a variable. |
FAQ
Can I use React, Vue, or Tailwind?
Yes, as long as the output is static text files. Build locally, then publish the dist/ folder (use Choose other folder). Keep bundles under the 750 KB per-file limit and avoid loading from a CDN unless you declare network.
Can a widget talk to the terminal or my files?
No — marketplace widgets are sandboxed iframes. For workflows that need the shell, files, or MCP tools, write a skill; the Vibe Agent runs it with full tool access.
Can I call my own API?
Yes. Declare network, use HTTPS, send CORS headers from your server, and degrade gracefully when offline. Say what you call in the listing description.
How do I include images or a logo?
Inline SVG in the HTML, CSS gradients, or small data-URI PNGs. Binary files are not part of the upload. Listing artwork (card, banner, screenshots) is uploaded separately in Account → Marketplace.
Do I need to ship Windows binaries or an installer?
No. VibeFuse is the runtime. Your package is text files that VibeFuse serves over widget://.
Can I unpublish or delete a widget?
Unpublish from Settings → Marketplace or Account → Marketplace hides the listing and stops new saves; existing installs keep working. Contact support to remove a listing entirely.
Next guides
Related: Widget types · Sell & earn · Marketplace · How to sell AI widgets