See how BTST composes backend and client plugins, with tested v3 examples and clear responsibilities for storage, permissions, routing and publishing.

A modular full-stack feature packages related database models, server operations and user interfaces so you can add them to an existing application. The useful question is how much integration work it removes, and which responsibilities remain with your team.
For a React application, a Blog feature is a concrete example. An editor component covers writing. A complete feature also needs post storage, draft rules, mutation endpoints, public routes, metadata and cache updates. Evaluating those pieces together gives you a better basis for choosing a library than a promise of “zero glue code.”
This guide uses BTST 3.1.2 to show that division. It replaces the older examples on this page, which used obsolete APIs and included hypothetical newsletter and roadmap plugins. Those examples were not a working installation.
Different approaches transfer different maintenance work to your application:
| Approach | What you reuse | What you maintain |
|---|---|---|
| UI components | Editor, forms and presentation | Data model, API, access policy and routes |
| Starter or generated code | An initial implementation | Copied code and its future changes |
| Full-stack plugin | Related server operations and client behavior | Application integration, configuration and upgrades |
| Hosted content service | A separate content backend | Service integration, access configuration and delivery |
A plugin fits when its content model and workflow are close to your requirements. If your publishing rules are unusual, inspect the extension points before adopting it. A small editor plus your own API can be easier to maintain than extensive overrides of an incompatible feature.
AI tools can help write either integration. Generated code still needs review against the installed package version, your access rules and your deployment environment. Reuse and generation can be combined; neither establishes that an application is ready for production.
BTST separates backend plugins from client plugins. The Blog backend supplies post and tag operations. The Blog client supplies pages and data loading. Your framework connects them to HTTP requests and rendered routes.
Here is the backend composition with the published 3.1.2 API:
// lib/stack.ts — local demonstration only
import { createBackendStack } from "@btst/stack/api"
import { createMemoryAdapter } from "@btst/adapter-memory"
import { blogBackendPlugin } from "@btst/stack/plugins/blog/api"
export const stack = createBackendStack({
basePath: "/api/data",
plugins: {
blog: blogBackendPlugin(),
},
adapter: (schema) => createMemoryAdapter(schema)({}),
})
export const { handler, dbSchema } = stack
This illustrates composition, not a production backend. The memory adapter does not provide durable storage. This example also supplies no application authorization policy: do not expose its mutation endpoints publicly. Connect your database and server access checks before deploying.
The client configuration uses the corresponding client API:
// lib/stack-client.ts
import { createClientStack } from "@btst/stack/client"
import { blogClientPlugin } from "@btst/stack/plugins/blog/client"
import type { QueryClient } from "@tanstack/react-query"
export function getStackClient(queryClient: QueryClient) {
return createClientStack({
api: {
baseURL: "http://localhost:3000",
basePath: "/api/data",
},
site: {
baseURL: "http://localhost:3000",
basePath: "/pages",
},
queryClient,
plugins: {
blog: blogClientPlugin(),
},
})
}
These snippets were type-checked against Stack 3.1.2. They describe two parts of the integration; importing them does not mount a route or render a page. Supply trusted deployment origins instead of the local URLs. Create a query client for each server request so one visitor's prefetched data is not reused for another visitor.
Use the framework guide to connect the HTTP handler, page routes, provider, styles and hydration: Next.js, TanStack Start or React Router. The Blog documentation describes the feature surface; match code examples to your installed release.
A reusable feature still depends on decisions that only your application can make.
Storage and migrations. Choose an adapter, generate or reconcile its schema, and apply reviewed migrations. Decide how backups and restoration work. A plugin's model definitions do not migrate a production database merely because you import them.
Identity and permissions. Decide who may read drafts, create posts, edit another author's post and publish. Enforce these decisions on the server. Hiding an editor button is not authorization. BTST's authorization guide explains its access configuration; the application supplies the identity and policy.
Files. Choose storage, validate uploads and enforce access at the upload endpoint. If you use the Media plugin, verify both the asset record and stored bytes. An object-storage URL alone does not establish that an asset is registered in your application's Media library.
Rendering and caches. A page loader can prefetch content, but your framework still needs to render the route and transfer the appropriate query state. Publishing must refresh the relevant list, article and metadata caches. Server rendering and hydration require deliberate configuration; they do not guarantee the absence of every loading state. See TanStack Query's server-rendering guidance.
Public URLs. Decide the page prefix and canonical origin. Check actual canonical links, social metadata and sitemap entries after deployment. An API mounted at /api/data and pages mounted at /pages have different jobs; their paths need to agree with the client configuration.
Evaluate a candidate plugin with the same small workflow you expect users to perform:
These checks cover different boundaries. A successful TypeScript build cannot prove your database persists data. A working editor cannot prove anonymous access is restricted. A database write cannot prove a cached public page has updated.
The snippets above were checked as isolated configuration examples; they are not evidence that your authentication, uploads, migrations or deployment have been tested.
Start with the feature you need now. Add a second plugin when its behavior solves a concrete requirement, then verify how identity, routes and data ownership interact. Similar naming conventions do not guarantee that independently configured plugins share an access policy or connect their records automatically.
For BTST, compare the available plugins with your requirements, then follow installation for your framework. Keep the backend/client boundary visible and test the resulting user workflow. That is the practical value of modular full-stack development: reuse established feature behavior while making your application's responsibilities explicit.