Skip to main content

sv

sv exposes a programmatic API for creating projects and running add-ons.

defineAddon

Creates an add-on definition. See create your own for a full guide.

import { transforms } from '@sveltejs/sv-utils';
import { defineAddon, defineAddonOptions } from 'sv';

export default defineAddon({
	id: 'my-addon',
	options: defineAddonOptions().build(),

	// called before run - declare dependencies, environment requirements, and dynamic options
	setup: ({ dependsOn, runsAfter, unsupported, addOption, isKit }) => {
		if (!isKit) unsupported('Requires SvelteKit');
		dependsOn('eslint');
		runsAfter('prettier');

		// dynamically add options based on workspace state or fetched data
		addOption('theme', {
			question: 'Which theme?',
			type: 'select',
			default: 'dark',
			options: [{ value: 'dark' }, { value: 'light' }]
		});
	},

	// the actual work — add files, edit files, declare dependencies
	run: ({ sv, options, cancel }) => {
		// add a dependency
		sv.devDependency('my-lib', '^1.0.0');

		// create or edit files using transforms from @sveltejs/sv-utils
		sv.file('src/lib/foo.ts', (content) => {
			return 'export const foo = true;';
		});

		// remove a file (respects the migration file filter)
		sv.removeFile('src/lib/obsolete.ts');

		// edit multiple existing files
		sv.files(
			{
				include: 'src/**/*.{js,ts,svelte}',
				exclude: 'src/**/+layout.{js,ts,svelte}',
				where: (content) => content.includes('old-value')
			},
			(content, path) => content.replaceAll('old-value', 'new-value')
		);

		sv.file(
			'src/routes/+page.svelte',
			transforms.svelte(({ ast, svelte }) => {
				svelte.addFragment(ast, '<p>Hello!</p>');
			})
		);

		// cancel at any point if something is wrong
		// cancel('reason');
	},

	// displayed after the add-on runs
	nextSteps: ({ options }) => ['Run `npm run dev` to get started']
});

Official add-ons included in dependsOn will be installed automatically. Community add-ons must be included as part of the same command.

dependsOn also implies runsAfter. Use runsAfter alone when you only care about ordering.

dependsOn and runsAfter both accept official and community add-on ids. ('sveltekit-adapter', 'better-auth', ...)

The sv object in run provides file, files, removeFile, dependency, devDependency, and execute. For file transforms (AST-based editing of scripts, Svelte components, CSS, JSON, etc.) and package manager helpers, see @sveltejs/sv-utils.

Typed dynamic options

If your add-on adds options dynamically in setup (e.g. from a fetch), you can pass a type parameter to defineAddon to get strong typing for those options:

const addon = defineAddon<{ theme: string }>()({
	id: 'my-addon',
	options: defineAddonOptions().build(),
	setup: ({ addOption }) => {
		addOption('theme', {
			question: 'Which theme?',
			type: 'string',
			default: 'dark'
		});
	},
	run: ({ options }) => {
		options.theme; // string
	}
});

The type parameter maps value types (boolean, string, number) to question definitions. Without it, defineAddon stays strict and only allows statically defined options.

defineAddonOptions

Builder for add-on options. Chained with .add() and finalized with .build().

import { defineAddonOptions } from 'sv';

const options = defineAddonOptions()
	.add('database', {
		question: 'Which database?',
		type: 'select',
		default: 'postgresql',
		options: [{ value: 'postgresql' }, { value: 'mysql' }, { value: 'sqlite' }]
	})
	.add('docker', {
		question: 'Add a docker-compose file?',
		type: 'boolean',
		default: false,
		// only ask when database is not sqlite
		condition: (opts) => opts.database !== 'sqlite'
	})
	.build();

Options are asked in order. The condition callback receives the answers collected so far — return false to skip the question (its value will be undefined).

Exporting your option types

Pass the values your options produce to defineAddonOptions. build() then checks your questions against them, both ways, and you get a small type to export for consumers of your add-on:

export type MyAddonOptions = { database: 'postgresql' | 'sqlite'; docker: boolean };

const options = defineAddonOptions<MyAddonOptions>()
	.add('database', {
		question: 'Which database?',
		type: 'select',
		default: 'sqlite',
		options: [{ value: 'postgresql' }, { value: 'sqlite' }]
	})
	.add('docker', { question: 'Add a docker-compose file?', type: 'boolean', default: false })
	.build();

Official add-ons follow the same pattern. OfficialAddonOptions is what add() accepts for them, keyed by add-on id:

// unanswered questions fall back to their default
const drizzle: OfficialAddonOptions['drizzle'] = { database: 'sqlite', docker: false };

create

Programmatically create a new Svelte project.

import { create } from 'sv';

create({
	cwd: './my-app',
	name: 'my-app',
	template: 'minimal',
	types: 'typescript'
});

add

Programmatically run add-ons against an existing project.

import { add, officialAddons } from 'sv';

await add({
	cwd: './my-app',
	addons: { prettier: officialAddons.prettier },
	options: { prettier: {} },
	packageManager: 'npm'
});

Edit this page on GitHub llms.txt