CLI • Add-ons
[create your own]
On this page
Community add-ons are currently experimental. The API may change. Don’t use them in production yet!
This guide covers how to create, test, and publish community add-ons for the Svelte CLI.
Quick start
The easiest way to create an add-on is by using the addon template:
npx sv create --template addon [path]The newly created project will have a README.md and CONTRIBUTING.md to guide you along.
Project structure
Typically, an add-on looks like this:
import { transforms } from '@sveltejs/sv-utils';
import { defineAddon, defineAddonOptions } from 'sv';
export default defineAddon({
id: 'addon-name',
shortDescription: 'a better description of what your addon does ;)',
options: defineAddonOptions()
.add('who', {
question: 'To whom should the addon say hello?',
type: 'string' // boolean | number | select | multiselect
})
.build(),
setup: ({ dependsOn, isKit, unsupported, addOption }) => {
if (!isKit) unsupported('Requires SvelteKit');
dependsOn('vitest');
// dynamically add options (e.g. based on workspace state or fetched data)
// addOption('key', { question: '...', type: 'boolean', default: true });
},
run: ({ isKit, cancel, sv, options, file, language, directory }) => {
// Add "Hello [who]!" to the root page
sv.file(
directory.kitRoutes + '/+page.svelte',
transforms.svelte(({ ast, svelte }) => {
svelte.addFragment(ast, `<p>Hello ${options.who}!</p>`);
})
);
},
nextSteps: ({ options }) => ['enjoy the add-on!']
});The CLI is split into two packages with a clear boundary:
sv= where and when to do it. It owns paths, workspace detection, dependency tracking, and file I/O. The engine orchestrates add-on execution.@sveltejs/sv-utils= what to do to content. It provides parsers, language tooling, and typed transforms. Everything here is pure - no file system, no workspace awareness.
This separation means transforms are testable without a workspace and composable across add-ons.
Development
You can run your add-on locally using the file: protocol:
cd /path/to/test-project
npx sv add file:../path/to/my-addonThis allows you to iterate quickly without publishing to npm.
The file: protocol also works for custom or private add-ons that you don’t intend to publish - for example, to standardize project setup across your team or organization.
The
demo-addscript automatically builds your add-on before running it.
Testing
The sv/testing module provides utilities for testing your add-on. createSetupTest is a factory that takes your vitest imports and returns a setupTest function. It creates real SvelteKit projects from templates, runs your add-on, and gives you access to the resulting files.
import { expect } from '@playwright/test';
import fs from 'node:fs';
import path from 'node:path';
import { createSetupTest } from 'sv/testing';
import * as vitest from 'vitest';
import addon from './index.js';
const { test, testCases } = createSetupTest(vitest)(
{ addon },
{
kinds: [
{
type: 'default',
options: {
'your-addon-name': { who: 'World' }
}
}
],
filter: (testCase) => testCase.variant.includes('kit'),
browser: false
}
);
test.concurrent.for(testCases)('my-addon $kind.type $variant', async (testCase, ctx) => {
const cwd = ctx.cwd(testCase);
const page = fs.readFileSync(path.resolve(cwd, 'src/routes/+page.svelte'), 'utf8');
expect(page).toContain('Hello World!');
});Your vitest.config.js must include the global setup from sv/testing:
import { function defineConfig(config: UserConfig): UserConfig (+4 overloads)function defineConfig(config: UserConfig): UserConfig (+4 overloads)defineConfig } from 'vitest/config';
export default function defineConfig(config: UserConfig): UserConfig (+4 overloads)function defineConfig(config: UserConfig): UserConfig (+4 overloads)defineConfig({
UserConfig.test?: InlineConfig | undefinedOptions for Vitest
UserConfig.test?: InlineConfig | undefinedOptions for Vitest
test: {
InlineConfig.include?: string[] | undefinedA list of glob patterns that match your test files.
InlineConfig.include?: string[] | undefinedA list of glob patterns that match your test files.
include: ['tests/**/*.test.{js,ts}'],
InlineConfig.globalSetup?: string | string[] | undefinedPath to global setup files
InlineConfig.globalSetup?: string | string[] | undefinedPath to global setup files
globalSetup: ['tests/setup/global.js']
}
});And the global test setup script tests/setup/global.js:
import { fileURLToPath } from 'node:url';
import { setupGlobal } from 'sv/testing';
const TEST_DIR = fileURLToPath(new URL('../../.test-output/', import.meta.url));
export default setupGlobal({ TEST_DIR });Publishing
Bundling
Community add-ons are bundled with tsdown into a single file. Everything is bundled except sv. (It is a peer dependency provided at runtime.)
package.json
Your add-on must have sv as a peer dependency and no dependencies in package.json:
{
"name": "@my-org/sv",
"version": "1.0.0",
"type": "module",
// bundled entry point (tsdown outputs .mjs for ESM)
"exports": {
".": { "default": "./dist/index.mjs" }
},
"publishConfig": {
"access": "public"
},
// cannot have dependencies
"dependencies": {},
"peerDependencies": {
// minimum version required to run by this add-on
"sv": "^0.13.0"
},
// Add the "sv-add" keyword so users can discover your add-on with https://www.npmx.dev/search?q=keyword:sv-add
"keywords": ["sv-add", "svelte", "sveltekit"]
}Package names
Packages must be published under an npm org:
# ✓ GOOD
npx sv add @my-org/sv
npx sv add @my-org/core
# ✗ BAD
npx sv add my-libIf your package is published with the sv scope, it can be omitted. The following all resolves to the same package:
npx sv add @my-org
npx sv add @my-org/sv
npx sv add @my-org/sv@latestFor a specific version, append @<version>:
npx sv add @my-org/sv@1.2.3Entry points
The CLI looks for ./sv first. If that is not found, it defaults to the . entry point. This means you have two options:
- Default export (for a dedicated add-on package):
{
"name": "@my-org/sveltekit-addon",
"exports": {
".": "./dist/addon.mjs"
}
}./svexport (for packages that also export other functionality):
{
"name": "@my-org/sveltekit-addon",
"exports": {
".": "./dist/main.mjs",
"./sv": "./dist/addon.mjs"
}
}Publish to npm
npm login
npm publish
prepublishOnlyautomatically runs the build before publishing.
Next steps
You can optionally display guidance in the console after your add-on runs:
import { color } from '@sveltejs/sv-utils';
export default defineAddon({
// ...
nextSteps: ({ options }) => [
`Run ${color.command('npm run dev')} to start developing`,
`Check out the docs at https://...`
]
});Version compatibility
Your add-on should specify a minimum sv version in peerDependencies. Your users will get a compatibility warning if their sv version has a different major version than what was specified.
Examples
See the official add-on source code for some real world examples.