Skip to main content
This walks through building a driver from the starter template, running it against Drawdy in development, and packaging it for distribution.

1. Get the starter

The fastest path is to clone the extension starter, a Vite project wired for the whole build:
The starter already depends on the protocol package for its types:
The protocol package is types only — it ships no runtime code. You import types from it and nothing lands in your bundle.

2. Write the module

A driver exports activate and onEvent. Here is a complete extension that adds a context-menu item, and each time it is clicked drops a sticky note on the canvas and flies the camera to it:
A few things to notice:
  • issueCommand is how you talk to Drawdy. Every call takes { type, driverId, requestId, req } and resolves to a response. driverId comes from your manifest; requestId is any value unique within your driver.
  • Subscriptions are set up by issuing a command. subscription:context-menu:clicked registers interest; the matching events then arrive in onEvent.
  • generateId mints ids for the canvas elements you create.
See the full command and event catalogue in the Protocol Reference.

3. The manifest

Every extension declares a manifest.json:
main is the entry bundle inside the .drawdyx zip. driverVersion is a semver version; every update you publish needs a higher one. driverId is the stable identity Drawdy uses for install, storage, and command routing. permissions declares what the driver may do; the starter adds a context menu (dom) and draws on the canvas (scene). Drawdy asks the user to grant each one the first time a command needs it. See Permissions.

4. Run it in development

The starter runs a Vite dev server (http://localhost:5173 by default — it prints the URL). On every save it rebuilds your extension, serves it at /built.drawdyx, and bumps a /version endpoint. Drawdy watches that endpoint and hot-reloads your driver on each build — no reinstall.

Point Drawdy at your dev server

1

Open a local board

Development links work only on local boards, not cloud boards. Open or create a local workspace.
2

Add the dev server

Open the command palette with ⌘K (macOS) or Ctrl+K (Windows/Linux) and run Add extension dev server. Enter your dev server URL, e.g. http://localhost:5173 (a bare localhost:5173 works too).
3

Edit and save

Drawdy polls <your-url>/version every second, and when it changes it fetches <your-url>/built.drawdyx and reloads the driver in place.
Remove a link with Remove extension dev server in the same palette. Links are stored per board in your browser, so each board keeps its own set. ws:// and wss:// URLs are accepted and normalized to http/https; the path is ignored, so only the host matters.
A cloud board never shows these commands — the extension host would have to trust an arbitrary local server, so hot reload is restricted to local boards you own.

Develop several extensions at once

Nothing stops you running more than one at a time. Each dev link is an independent connection: Drawdy polls each one separately and installs the driver it serves under that driver’s own driverId, so all of them live on the same board side by side. To add a second extension:
1

Run its dev server on a different port

The starter pins port 5173 with strictPort, so a second npm run dev would fail on the same port. Start it on another port instead:
(or change server.port in that extension’s vite.config.ts).
2

Add a second dev server

Run Add extension dev server again and enter the new URL, e.g. http://localhost:5174. The palette now lists a Remove extension dev server entry per link, so you can drop them one at a time.
Give each extension a distinct driverId in its manifest.json. Two dev servers serving the same driverId install over each other — Drawdy routes by driverId, so the id is what keeps them separate.

5. Package for distribution

This produces dist/<driver-id>.drawdyx — the zip of your manifest.json and main.js. That single file is the whole extension. Anyone can install it into Drawdy.

The .drawdyx format

A .drawdyx is a plain zip with two entries:
The bundle must be CommonJS with named exports — Drawdy loads it and reads its activate and onEvent exports. The starter’s Rollup config emits exactly this; if you roll your own build, target format: "cjs" with exports: "named".

6. Prepare to submit

You submit from a GitHub repository, not from the .drawdyx you built. Drawdy fetches your source from GitHub and builds it with its own build rules. Choose how you want to share it: You can do both from the Account → Extensions page on drawdy.io, or through the API. If you don’t see that page, submitting isn’t enabled for your account yet.

Repository rules

These rules apply to public and private extensions alike. The repository must be public on GitHub.
  • manifest.json at the repository root. It must be valid JSON with these fields: Drawdy won’t install an extension whose manifest has no description or permissions. Use an empty array if the driver needs no permission. See Permissions.
  • An entry file at src/index.ts, src/index.tsx or src/index.js. Drawdy bundles it with esbuild into one CommonJS file targeting ES2020 in the browser.
  • No npm dependencies. Relative imports work, but the only package you may import is @drawdy/driver-protocol, which is types only. Drawdy does not run npm install, so any other package import fails the build. Copy the code you need into your repository instead.
  • No bundler plugins. Your Vite or Rollup config is not used. If the starter generates a module at build time (a virtual: import, such as webview HTML), commit the generated file as source, e.g. src/webview-html.ts, and import that.
  • Size limits. At most 500 files and 20 MB across the whole repository, not only src/. The built bundle must stay under 5 MB.
Optional listing files, all read from the same commit:
  • Icon: the icon path from the manifest, or else icon.png, icon.webp or icon.svg at the root. PNG, WebP or SVG, up to 256 KB. SVGs containing scripts are rejected.
  • README.md at the root, up to 200 KB. It becomes the extension’s overview page. Relative links and images are rewritten to point at your repository on GitHub.
  • CHANGELOG.md at the root, up to 200 KB. It appears in the Changelog tab.
Pick a driverId under your own name, e.g. yourname.my-extension. The first person to submit an id owns it, and nobody else can publish under it afterwards. drawdy. ids are for Drawdy’s own extensions.

Get an API token

Skip this if you use the page on drawdy.io. To submit with the API, from a script or an AI agent, you need a Drawdy token. You approve it once in the browser, so the agent never needs your password. Request a device code:
Open verification_uri_complete while signed in to Drawdy and click Approve. Meanwhile, poll for the token every interval seconds:
Until you approve, it answers with the error authorization_pending. Keep polling, and add 5 seconds to the interval on slow_down. After approval it returns { "access_token": "…", "token_type": "Bearer", "expires_in": 2592000 }. The token lasts 30 days. Send it on every API call in sections 7 and 8 as Authorization: Bearer <access_token>. The API is at https://api.drawdy.io. Every failed call returns { "error": "<message>" }, and the message says what to fix.
To let an AI agent do it, give it this page and a prompt such as: “Submit the extension in this repository to Drawdy as a public extension, using the API in the Drawdy quickstart. Push first, show me the approval link, then report the submission status and any build error or reviewer note.” For a private extension, ask it to add the repository as a private extension instead. Approve the link it shows you.

7. Submit a public extension

A public extension is listed in the Marketplace, where anyone can install it. The Drawdy team reviews every version before it goes live. Your GitHub account must have push access to the repository. You can submit up to 10 times per day.

With the form

1

Push your code

Commit and push. Drawdy only sees what is on GitHub, so uncommitted or unpushed changes are not submitted.
2

Open the submit form

Go to Account → Extensions, open the Submit tab and choose Public.
Account → Extensions, Submit tab: the requirements list, then a New submission form with Visibility set to Public and a Connect GitHub button.

The Submit tab, with Public selected and GitHub not connected yet.

3

Connect GitHub

Click Connect GitHub and approve access. Drawdy uses this only to check that your GitHub account has push access to the repository. You can submit only repositories you can push to.
4

Enter the repository

Paste the repository URL, e.g. https://github.com/you/my-extension. Optionally enter a branch, tag or commit; leave it empty to submit the latest commit on the default branch.
5

Submit for review

Click Submit for review. Drawdy pins the submission to that exact commit, so pushing again later does not change what is reviewed.
The New submission form with GitHub connected as @ada-okafor, Repository URL https://github.com/ada-okafor/sticky-timer, branch main, and a Submit for review button.

GitHub connected, repository and branch filled in, ready to submit.

With the API

You need a Drawdy token and a GitHub token.
1

Get a GitHub token

Any GitHub token that can read the repository works, for example gh auth token, or a fine-grained personal access token limited to that repository. Drawdy uses it only to check that your GitHub account has push access to the repository, and does not store it.
2

Submit

ref is a branch, tag or commit SHA. HEAD means the latest commit on the default branch. A 201 returns the submission:
3

Follow the review

This returns { "submissions": [...] }, newest first. Each one has a status (see the review process), plus buildError when the build failed and reviewNote once a reviewer has decided. GET /api/extensions/submissions lists your latest submissions across all extensions.

The review process

Every public submission goes through the same steps. You follow them in Account → Extensions → My extensions, where each extension expands into its submission history.
The My extensions tab listing Sticky Timer with its submission history, each version showing a status badge, the repository and commit, and a reviewer note, and Wireframe Kit marked Delisted by Drawdy.

My extensions: Sticky Timer expanded, with v1.2.1 in review, v1.2.0 superseded and two published versions. Wireframe Kit was delisted by Drawdy.

1

Building

Drawdy checks the manifest, downloads your repository at the pinned commit and bundles it under the rules above.If the build fails, the status changes to Build failed and the Build error says why, for example a disallowed import or a missing entry file. Fix it, push, and submit again.
2

Scan

Drawdy scans the built bundle for calls that need a closer look. A match never fails the submission. It points the reviewer at that line.
3

In review

A member of the Drawdy team reviews the submission. They read:
  • the built bundle, line by line where the scan flagged something
  • the manifest, and whether each permission matches what the extension does
  • the listing: name, description, icon, README and changelog
  • the repository and commit it was built from
Keep the bundle readable: don’t commit minified or obfuscated source, and declare only the permissions the extension uses.
4

Decision

  • Approved → Published. Drawdy publishes the build straight to the Marketplace. The listing shows Live · v1.0.0, and that version is pinned to the reviewed commit.
  • Rejected. The Reviewer note says what to change. Fix it and submit again; a rejected version has not been published, so you can resubmit with the same driverVersion.
Submitting again while an earlier submission is still building or in review replaces the earlier one, which becomes Superseded. Only the newest submission is reviewed.

Publish an update

Bump driverVersion in manifest.json above the published version, add an entry to CHANGELOG.md, push, and submit again. Every update goes through the full review. Until the new version is approved, the Marketplace keeps serving the current one.

After publishing

  • Delist an extension from My extensions to remove it from the Marketplace, so nobody new can install it. List again puts it back.
  • Users can report an extension from its Marketplace page. Drawdy reviews reports and can delist an extension. An extension delisted by Drawdy shows Delisted by Drawdy and can only be listed again by Drawdy; contact support if you think it was a mistake.
  • Drawdy can mark a publisher as verified. Verified publishers get a badge next to their name on every listing.

8. Add a private extension

A private extension skips the Marketplace and the review. Use it for extensions you only want on your own boards, or to try one out before submitting it publicly. No GitHub sign-in is needed, and Drawdy stores only the repository URL.

With the form

1

Push your code

The repository must be public on GitHub and follow the same rules as a public extension.
2

Add it to your account

Go to Account → Extensions, open the Submit tab, choose Private, paste the repository URL and click Add extension. No GitHub sign-in is needed.Drawdy checks that manifest.json is at the root with a description and permissions, and that an entry file exists. It stores only the repository URL.
The New submission form with Visibility set to Private, a Repository URL, an empty Branch, tag or commit field, and an Add extension button.

Private needs no GitHub sign-in, only the repository URL.

3

Install it on a board

On any of your boards, open Extensions → Private and install it. Your browser downloads the source from GitHub and builds it with the same rules Drawdy’s servers use, then caches the build.

With the API

You need a Drawdy token. No GitHub token is needed.
Add "ref" to choose a branch, tag or commit (see which code is installed). Drawdy checks that manifest.json is at the root with a description and permissions, and that an entry file exists. A 201 returns { "extension": {...} } with its id, driverId, name and version. Then install it on a board from Extensions → Private. GET /api/extensions/private lists the private extensions on your account, and DELETE /api/extensions/private/<id> removes one.

Which code is installed

The optional Branch, tag or commit (ref) decides which code is installed:
  • Empty or a branch: installs the latest commit on that branch. Installed copies check for new commits at most every 6 hours and update to them, without a review.
  • A tag or commit SHA: stays on that code until you change it.

Things to know

  • Sharing. When you install a private extension on a cloud board you can edit, editors of that board get it too. Drawdy tells them it is a private extension from you and that it has not been reviewed. Viewers don’t get it.
  • Removing it. Removing it from your account stops new installs. Boards where it’s already installed keep their copy until it’s uninstalled there.
  • Limits. Up to 50 private extensions per account.
  • GitHub rate limits. Each install or update check calls the GitHub API without signing in, which GitHub limits to 60 requests per hour per network. If you reach it, Drawdy waits and tries again later.

Learn from the examples

The protocol repo ships three runnable drivers, drawdy-hello, drawdy-math-symbols, and drawdy-physics-engine, each built with this same template. See Examples for what each one shows, or browse examples/ on GitHub.