Skip to main content
Version: 2026-09-21 (current)

Your tool's files

A hosted tool is a folder of plain browser files. The AI Builder writes the folder for you; Download Code hands it to you as a zip, and Upload Code takes it back. Nothing in it is compiled, bundled, or installed — what you upload is what runs.

The folder​

A typical tool:

my-tool/
├── manifest.json how to load the tool, and the locations it offers
├── app.js the entry: defines the custom element
├── styles.css the tool's styles
├── index.html the tool's markup
└── lib/
└── scoring.js another module, imported by app.js
FileRole
The entry (app.js)Required. An ES module that defines your element and calls customElements.define. It is the only file Voshi imports; everything else it loads is what the entry imports or the manifest names.
Stylesheets (.css)Voshi loads them and adopts them into your element's shadow root before it renders, so your styles apply without you doing anything. See Assets.
Templates (.html)Markup fragments — no <html>, <head>, <body>, <script>, or <style>. Voshi loads them and you render one with api.assets.template('index.html').
Other modules (.js, .mjs)Imported by the entry or by each other, with relative paths: import { score } from './lib/scoring.js'. The .js extension is required; the browser has no module resolver. A .json file can be imported too: import words from './words.json' with { type: 'json' }.
Everything elseImages, fonts, data files. Reach one with api.assets.url('map.png').

Everything in the folder is uploaded, at its own path. Left out on purpose: dotfiles and dot-folders (.git, .env), node_modules, __MACOSX, and AGENTS.md. Anything else you leave in the folder — a README, a scratch preview.html — is published with the tool, so delete what you don't want served.

Limits​

Limit
One file10 MB
The whole tool50 MB, 200 files
manifest.json64 KB, at most 200 locations
Stylesheets and templates together20, because every one is fetched on every launch
PathsLetters, digits, ., _, -; no name starting with -; at most 4 folders deep. Two paths that differ only in case are refused.

manifest.json​

The manifest sits at the root of the folder and says two things: how Voshi should load the tool, and which locations it offers. It does not list the tool's files.

{
"manifest_version": 2,
"app": {
"entry": "app.js",
"tag": "flashcard-drill",
"styles": ["styles.css"],
"templates": ["index.html"]
},
"locations": {
"home": {
"type": "assessment",
"label": "Flashcard Drill",
"description": "Ten accounting terms, flipped and self-marked.",
"points": 10
}
}
}
KeyMeaning
manifest_version2. (A version 1 manifest has no app section and is still accepted.)
app.entryThe entry module. Without it, Voshi uses the folder's only .js file, or else app.js.
app.tagThe custom element tag the entry defines. Optional, but if present it must match.
app.stylesThe stylesheets to load, in order. Leave it out to load every .css in the folder; [] loads none.
app.templatesThe templates to load. Leave it out to load every .html in the folder; [] loads none.
locations, grouped_locationsThe locations the tool offers, in the same shape a self-hosted tool serves. See Locations for what uploading one does to the Locations tab.

A manifest can declare the locations and leave app out, or the other way round. An upload with no manifest.json at all keeps the tool's locations exactly as they are.

Editing the code yourself​

The AI Builder is one way to write a tool, not the only one. The code it produces is ordinary and readable, and the loop for editing it elsewhere is:

Download Code

From the Running Tool panel. The zip holds the draft, plus an AGENTS.md written for a coding assistant: it states the rules of a hosted tool, lists the tool's locations, and says what the platform will refuse. Point Claude Code, Cursor, or whatever you use at the folder and it knows what it is editing. The file is skipped on upload, so leave it there.

Edit

Any editor. There is no build step, so there is nothing to install or run. For layout work, AGENTS.md includes a small preview.html you can serve from the folder with any static server; grades and storage only work in a real launch, so test those in the Sandbox.

Upload Code

Choose the folder. The upload replaces the draft, and the Running Tool panel reloads. If the upload is refused, the message names the file and the rule — a missing import, a bare module name, an entry that defines a different tag than the manifest says.

warning

Every upload replaces the whole draft. The previous draft is gone; there is no history of drafts. Keep your own copy of code you care about, and publish when a draft is worth keeping — a published version is permanent.

The api.assets rule​

Your files are served from a directory named by their content hash, so every upload and every version lives at a different address. Never write an absolute URL to one of your own files, and never fetch styles.css or index.html yourself: Voshi has already loaded them. Use api.assets — template(), styles, url() — and the right copy is always the one you get.

What the platform refuses​

These are the upload's own messages, so you can recognize them:

  • manifest.json names styles.css, which is not in the upload
  • More than one .js file could be the entry (a.js, b.js): add a manifest.json whose app.entry names it, or name it app.js
  • The upload has no .js file: upload the module that defines the custom element
  • app.js imports ./lib/cards.js, which is not in the upload
  • app.js imports ./styles.css: the page loads stylesheets itself (every .css, or manifest.json app.styles)
  • app.js imports 'lit', a bare module name, and nothing on the page can resolve one
  • app.js imports ../../x.js, which is outside the tool
  • manifest.json says app.tag is 'my-tool', and app.js defines 'other-tool': make them the same
  • App.js and app.js differ only in case; rename one of them
  • media/video.mp4 is larger than 10 MB / The tool is larger than 50 MB in total