Create a Plugin
Scaffold a VitNode plugin, register its package in your host app, and serve its first TanStack Start page.
A plugin is the starting point for a VitNode feature. It keeps routes, API modules, data, translations, and AdminCP extensions together in one installable package. Nice boundaries; fewer archaeological digs later.
Plugins need a workspace
Run the generator from a repository with turbo.json. When creating an app,
turn on Turborepo first; a plain single-folder app has nowhere for
plugins/*.
Generate the package
Run this at the workspace root and enter a package name such as
@acme/site-notes when prompted:
bun create vitnode-app@canary --pluginThe CLI creates plugins/site-notes, adds it as a workspace dependency, and
gives it a route, locale, and config skeleton. It does not enable the
feature for the host—that explicit switch is next.
Keep the route in the plugin
The generated routes.ts is the public contract. Add another page() here when
the plugin needs another URL; never copy its page into apps/web/src/routes.
import { definePluginRoutes, lazy, page } from '@vitnode/core/routing'
export const routes = definePluginRoutes([
page('/site-notes', {
component: lazy(() => import('./pages/home-page')),
}),
])Register the plugin with the host
Import the plugin factory in the host config and add it to plugins:
import { siteNotesPlugin } from '@acme/site-notes/config'
import { buildConfig } from '@vitnode/core/vitnode.config'
export const vitNodeConfig = buildConfig({
plugins: [
siteNotesPlugin(),
],
})That is the only composition step - the factory carries the plugin's routes, content types and AdminCP navigation, and the feature stays in its package. Your build reads this list and generates one literal import per plugin for each of those, so a page or an editing screen loads with the route that needs it rather than with the config. See Configuration.
Its translations need one more line, in src/locales/packages.ts - see
Languages & Localization.
Run it and visit the route
bun devOpen http://localhost:3000/site-notes. The page comes from the plugin, gets
its own chunk, and never moves house. Tiny victory dance optional.