Examples

Build a To-Do app

A complete, working .vibe app you can study, submit, and fork. It focuses on the two things every app needs: a correct manifest and clean VibeOS SDK integration for saving data.

This walkthrough builds a small To-Do list that persists tasks across sessions using the VibeOS filesystem SDK — no servers, no API keys, one JSON file. Grab the finished template first, then read how each piece works.

Use it as a template

to-do.vibe is a ready-to-submit manifest. Open it in any editor, change the name, iconEmoji, description and the inline html, then paste it into the submit form to create your own draft.

What you'll build

  • A single-window app with an add form and a checkable task list.
  • Persistence to the app's private sandbox via window.vos.fs.
  • A debounced save so rapid edits don't thrash the filesystem.
  • A manifest that requests the minimum permissions it actually needs.

Step 1 — The manifest

Everything ships in one JSON object. The metadata fields describe the listing; the html field holds the entire app as a single inline document. Here's the shell (the real html is the full document from the download):

json
{
  "vibe": 1,
  "name": "To-Do",
  "iconEmoji": "✅",
  "iconBg": "linear-gradient(135deg,#10b981,#0d9488)",
  "description": "A fast, minimal to-do list that saves your tasks to your app sandbox.",
  "version": "1.0.0",
  "author": "VibeOS",
  "genre": "productivity",
  "width": 460,
  "height": 560,
  "closeBehavior": "close",
  "permissions": { "read": [], "write": [] },
  "html": "<!doctype html>…your inline app…"
}

See the .vibe manifest reference for every field. The only part that needs real thought here is permissions.

Step 2 — Request the right permissions

The To-Do app only ever reads and writes its own data file — it never touches the user's Documents or Pictures. So it declares the filesystem capability with empty arrays, which scopes it to the app's private sandbox and skips the directory-approval prompt entirely.

json
// Empty arrays declare the filesystem capability but grant ONLY
// the app's private sandbox at home/.apps/<appId>/.
// No user prompt for shared directories, nothing else is reachable.
"permissions": { "read": [], "write": [] }

Least privilege wins reviews

Only request shared directories (e.g. ["home/Documents"]) if your app genuinely needs them. Sandbox-only apps install with no friction and pass review faster. This app also omits ai because it makes no model calls.

Step 3 — Wire up the SDK

Inside the inline HTML, the app talks to VibeOS through the global window.vos object. Every method returns a Promise.

1

Wait for the SDK

The SDK is injected asynchronously. Start your app from the vos-ready event, falling back to running immediately if window.vos already exists.

html
<script>
(function () {
  // Wait for the SDK. If window.vos already exists, start now;
  // otherwise listen for the one-shot "vos-ready" event.
  if (window.vos) init();
  else window.addEventListener("vos-ready", init);
})();
</script>
2

Find the sandbox and load saved tasks

window.vos.app.getInfo() returns the app's sandboxPath — the private directory backing the empty-array permissions. Read your data file from there on boot; a missing file just means it's the first run.

javascript
var FILE = "todos.json";
var sandboxPath = null;

function init() {
  if (!window.vos) { setStatus("SDK unavailable"); return; }

  // getInfo() returns the app's private sandbox path. No permission needed.
  window.vos.app.getInfo()
    .then(function (info) {
      sandboxPath = info.sandboxPath;
      if (!sandboxPath) { setStatus("No sandbox"); return; }
      // Try to read previously saved tasks from the sandbox.
      return window.vos.fs.readFile(sandboxPath, FILE);
    })
    .then(function (content) {
      if (typeof content === "string" && content) {
        var parsed = JSON.parse(content);
        if (Array.isArray(parsed.todos)) todos = parsed.todos;
      }
      loaded = true;
      setStatus("Ready");
      render();
    })
    .catch(function () {
      // File doesn't exist on first run — start with an empty list.
      loaded = true;
      setStatus("Ready");
      render();
    });
}
3

Save on every change (debounced)

When tasks change, schedule a write instead of saving immediately. The debounce coalesces quick edits into a single writeFile call.

javascript
var dirty = false;
var saveTimer = null;

// Debounce writes so rapid edits don't hammer the filesystem.
function scheduleSave() {
  dirty = true;
  setStatus("Saving…");
  clearTimeout(saveTimer);
  saveTimer = setTimeout(save, 250);
}

function save() {
  if (!sandboxPath || !dirty) return;
  var payload = JSON.stringify({ version: 1, todos: todos }, null, 2);
  window.vos.fs.writeFile(sandboxPath, FILE, payload)
    .then(function () { dirty = false; setStatus("Saved"); })
    .catch(function (err) { setStatus("Save failed: " + err.message); });
}

Always handle the missing-file case

On first launch the data file doesn't exist yet, so readFile rejects. Catch it and start with empty state rather than showing an error — see the .catch() in step 2.

Step 4 — Submit it

Paste your manifest into the submit form as a draft, then follow submission & review. For the full SDK surface — filesystem and AI — see the SDK reference.