sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
The parser choice is baked into the transform type - you can't accidentally
parse a vite config as svelte because you never call a parser yourself.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
If your add-on adds dynamic options via addOption during setup, pass their
types as a type argument:
constaddon=defineAddon<{extra:boolean}>()({...});// Take note of the extra call here: 👆👆// This works around Typescript's lack of partial type argumentsaddon.options.extra.default;// boolean
If your add-on adds dynamic options via addOption during setup, pass their
types as a type argument:
constaddon=defineAddon<{extra:boolean}>()({...});// Take note of the extra call here: 👆👆// This works around Typescript's lack of partial type argumentsaddon.options.extra.default;// boolean
(), // called before run - declare dependencies, environment requirements, and dynamic options
setup(property)setup?:((workspace:Workspace&{dependsOn:(id:AddonId)=>void;unsupported:(reason:string)=>void;runsAfter:(id:AddonId)=>void;addOption:<Kextendsstring>(key:K,question:BaseQuestion<any>&Question<any>)=>void;})=>MaybePromise<void>)|undefined
Setup the addon. Will be called before the addon is run.
Ordering only: run after these add-ons when they are part of the same run.
('prettier'); // dynamically add options based on workspace state or fetched data
addOption(parameter)addOption:<"theme">(key:"theme",question:BaseQuestion<any>&Question<any>)=>void
Dynamically add an option to be prompted to the user
('theme',{question(property)question:string:'Which theme?',type(property)type:"select":'select',default(property)default:any:'dark',options(property)options:{value:any;label?:string;hint?:string;}[]:[{value(property)value:any:'dark'},{value(property)value:any:'light'}]});},// the actual work — add files, edit files, declare dependenciesrun(property)run:(workspace:Workspace&{options:OptionValues<{}>&Record<string,unknown>;sv:SvApi;cancel:(reason:string)=>void;})=>MaybePromise<void>
Run the addon. The actual execution of the addon... Add files, edit files, etc.
Edits matching files in the workspace.
The include and exclude patterns are glob patterns relative to the workspace root.
For each matching file, the edit callback is called with the file content,
and should return the new content (or false to abort editing that file).
Note: always adds excludes for node_modules and dot-prefixed directories
Returns true if searchString appears as a substring of the result of converting this
object to a String, at one or more positions that are
greater than or equal to position; otherwise, returns false.
searchString
search string
position
If position is undefined, 0 is assumed, so as to search all of the String.
sv-utils = what to do to content, sv = where and when to do it.
Each transform wraps: parse -> callback({ast/data, utils}) -> generateCode().
The parser choice is baked into the transform type - you can't accidentally
parse a vite config as svelte because you never call a parser yourself.
Transforms are curried: call with the callback to get a (content: string) => string | false
function that plugs directly into sv.file().
import{transforms}from'@sveltejs/sv-utils';// use with sv.file() - curried form plugs in directlysv.file(files.viteConfig,transforms.script(({ast,js})=>{js.vite.addPlugin(ast,{code:'kitRoutes()'});}));// standalone usage / testingconstresult=transforms.script(({ast,js})=>{js.imports.addDefault(ast,{as:'foo',from:'foo'});})(fileContent);
(({ast(parameter)ast:AST.Root,svelte(parameter)svelte:typeofindex_d_exports$4})=>{svelte(parameter)svelte:typeofindex_d_exports$4.addFragment(alias)index_d_exports$4.addFragment(ast:AST.Root|AST.BaseElement,content:string,options?:{mode?:"append"|"prepend";language?:"ts"|"js";}):voidexportindex_d_exports$4.addFragment(ast(parameter)ast:AST.Root,'<p>Hello!</p>');})); // cancel at any point if something is wrong
// cancel('reason');
}, // displayed after the add-on runs
nextSteps(property)nextSteps?:((workspace:Workspace&{options:OptionValues<{}>&Record<string,unknown>;})=>string[])|undefined
Next steps to display after the addon is run.
:({options(parameter)options:OptionValues<{}>&Record<string,unknown>})=>['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:
If your add-on adds dynamic options via addOption during setup, pass their
types as a type argument:
constaddon=defineAddon<{extra:boolean}>()({...});// Take note of the extra call here: 👆👆// This works around Typescript's lack of partial type argumentsaddon.options.extra.default;// boolean
Add-on options (includes dynamically added options from setup)
.theme(property)theme:string;// 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().
This type is a bit complex, but in usage, it's quite simple!
The idea is to add() options one by one, with the key and the question.
.add('demo',{question:'Do you want to add a demo?',type:'boolean',// string, number, select, multiselectdefault:true,// condition: (o) => o.previousOption === 'ok',})
('database',{question(property)question:"Which database?":'Which database?',type(property)type:"select":'select',default(property)default:"postgresql":'postgresql',options(property)options:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}]:[{value(property)value:"postgresql":'postgresql'},{value(property)value:"mysql":'mysql'},{value(property)value:"sqlite":'sqlite'}]}).add(method)add<"docker",{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;readonlycondition:(opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>)=>boolean;}>(key:"docker",question:{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;readonlycondition:(opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>)=>boolean;}):OptionBuilder<...>
This type is a bit complex, but in usage, it's quite simple!
The idea is to add() options one by one, with the key and the question.
.add('demo',{question:'Do you want to add a demo?',type:'boolean',// string, number, select, multiselectdefault:true,// condition: (o) => o.previousOption === 'ok',})
('docker',{question(property)question:"Add a docker-compose file?":'Add a docker-compose file?',type(property)type:"boolean":'boolean',default(property)default:false:false, // only ask when database is not sqlite
condition(property)condition:(opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>)=>boolean:(opts(parameter)opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>)=>opts(parameter)opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",any>>.database(property)database:"postgresql"|"mysql"|"sqlite"!=='sqlite'}).build(method)build():{database:{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];};docker:{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;readonlycondition:(opts:OptionValues<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"postgresql";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"mysql";},{readonlyvalue:"sqlite";}];}>&Record<...>>)=>boolean;};}
Finalize all options of your add-on.
();
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:
This type is a bit complex, but in usage, it's quite simple!
The idea is to add() options one by one, with the key and the question.
.add('demo',{question:'Do you want to add a demo?',type:'boolean',// string, number, select, multiselectdefault:true,// condition: (o) => o.previousOption === 'ok',})
('database',{question(property)question:"Which database?":'Which database?',type(property)type:"select":'select',default(property)default:"sqlite":'sqlite',options(property)options:[{readonlyvalue:"postgresql";},{readonlyvalue:"sqlite";}]:[{value(property)value:"postgresql":'postgresql'},{value(property)value:"sqlite":'sqlite'}]}).add(method)add<"docker",{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;}>(key:"docker",question:{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;}):OptionBuilder<Record<"database",{readonlyquestion:"Which database?";readonlytype:"select";readonlydefault:"sqlite";readonlyoptions:[{readonlyvalue:"postgresql";},{readonlyvalue:"sqlite";}];}>&Record<"docker",{readonlyquestion:"Add a docker-compose file?";readonlytype:"boolean";readonlydefault:false;}>,MyAddonOptions>
This type is a bit complex, but in usage, it's quite simple!
The idea is to add() options one by one, with the key and the question.
.add('demo',{question:'Do you want to add a demo?',type:'boolean',// string, number, select, multiselectdefault:true,// condition: (o) => o.previousOption === 'ok',})
('docker',{question(property)question:"Add a docker-compose file?":'Add a docker-compose file?',type(property)type:"boolean":'boolean',default(property)default:false:false}).build(method)build():{database:QuestionFor<"postgresql"|"sqlite">;docker:QuestionFor<boolean>;}
Finalize all options of your add-on.
();
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 defaultconstdrizzleconstdrizzle:Partial<OptionValues<{database:QuestionFor<Database>;postgresql:QuestionFor<"postgres.js"|"neon">;mysql:QuestionFor<"mysql2"|"planetscale">;sqlite:QuestionFor<"node-sqlite"|"better-sqlite3"|"libsql"|"turso">;docker:QuestionFor<boolean>;}>>:OfficialAddonOptions(alias)typeOfficialAddonOptions={prettier:Partial<OptionValues<NoOptions>>;eslint:Partial<OptionValues<NoOptions>>;vitest:Partial<OptionValues<{usages:QuestionFor<("unit"|"component")[]>;}>>;playwright:Partial<OptionValues<{demo:QuestionFor<boolean>;}>>;tailwindcss:Partial<OptionValues<{plugins:QuestionFor<("typography"|"forms")[]>;}>>;"enhanced-img":Partial<OptionValues<NoOptions>>;...7more...;experimental:Partial<...>;}importOfficialAddonOptions
What add() accepts as options for official add-ons, keyed by add-on id.