TanStack Start Kitchen Sink
import { defineCMS, defineCollection, defineGlobal } from "@timbl/core";
export default defineCMS({
collections: [
defineCollection({
key: "projects",
labels: { singular: "Project", plural: "Projects" },
admin: { titleField: "title", defaultSort: "-createdAt" },
fields: [
{ key: "title", type: "text", required: true },
{ key: "slug", type: "slug", required: true },
{ key: "date", type: "date" },
{ key: "summary", type: "textarea" },
{ key: "body", type: "markdown" },
{ key: "featuredImage", type: "upload" },
{ key: "tags", type: "relation", to: "tags", many: true },
{ key: "status", type: "select", options: ["draft", "published"], defaultValue: "draft" },
],
}),
defineCollection({
key: "caseStudies",
labels: { singular: "Case Study", plural: "Case Studies" },
admin: { titleField: "title", defaultSort: "-createdAt" },
fields: [
{ key: "title", type: "text", required: true },
{ key: "slug", type: "slug", required: true },
{ key: "summary", type: "textarea" },
{ key: "body", type: "markdown" },
{ key: "featuredImage", type: "upload" },
{ key: "project", type: "relation", to: "projects" },
{ key: "status", type: "select", options: ["draft", "published"], defaultValue: "draft" },
],
}),
defineCollection({
key: "posts",
labels: { singular: "Post", plural: "Posts" },
admin: { titleField: "title", defaultSort: "-createdAt" },
fields: [
{ key: "title", type: "text", required: true },
{ key: "slug", type: "slug", required: true },
{ key: "body", type: "markdown" },
{ key: "tags", type: "relation", to: "tags", many: true },
{ key: "status", type: "select", options: ["draft", "published"], defaultValue: "draft" },
],
}),
defineCollection({
key: "tags",
labels: { singular: "Tag", plural: "Tags" },
admin: { titleField: "name", defaultSort: "name" },
fields: [
{ key: "name", type: "text", required: true },
{ key: "slug", type: "slug", required: true },
],
}),
],
globals: [
defineGlobal({
key: "siteSettings",
label: "Site Settings",
fields: [
{ key: "siteName", type: "text" },
{ key: "siteDescription", type: "textarea" },
{ key: "siteUrl", type: "text" },
{
key: "socialLinks",
type: "array",
of: {
key: "socialLink",
type: "group",
fields: [
{ key: "label", type: "text", required: true },
{ key: "url", type: "text", required: true },
],
},
},
],
}),
],
plugins: [],
}); A self-contained TanStack Start app that exercises the full @timbl/client surface against a timbl CMS instance. Located at examples/tanstack-start-kitchen-sink/ in the repo.
What it demonstrates
- Typed reads via server functions + route loaders:
collection().findMany()withstatus/sort/limit/offset/q/select/include+depth. - Detail by slug:
findBySlug()with relation expansion, rendered through a discriminated-union view (ok | notfound | error). - Relations:
caseStudies -> project(single) andposts/projects -> tags(many), narrowed through honest type guards at the server-fn boundary. - Globals:
siteSettingsin the root route. - Markdown: renders the CMS-provided
bodyHtmlcompanion (already sanitized). - Assets:
assets.findMany()gallery. - Auth (local dev):
auth.session()in the nav;/login+/logoutproxy Better Auth via server routes and re-homeSet-Cookie;/admindoescreate/delete,assets.upload(),exportContent()via server functions (cookie forwarded withgetRequestHeader). - Errors:
TimblHttpError-> discriminatednotfound/errorviews withcode+requestId(exhaustiveswitch). - System:
/healthcallshealth()/ready()/openApi(). - Server fns return serializable DTOs (the server fn is the serialization boundary — CMS entry types with
unknownnever reach the client).
Run
# 1. Start your timbl CMS (repo root)bun installbun run dev # CMS on http://localhost:3000
# 2. Run this example (from examples/tanstack-start-kitchen-sink)bun installcp .env.example .envbun run dev # app on http://localhost:4321Auth writes (/admin) need a seeded user and same-site local dev. For production cross-origin, deploy behind the same domain or use bearer tokens. Cookie auth does not work cross-origin.
bun run build produces client + server bundles without calling the CMS (loaders run on demand), so it builds without a running instance.
Schema
content.config.ts mirrors the collections this app uses and is consumed type-only via InferCMS<typeof cms>. src/lib/cms.ts is client-safe (types + pure guards only); src/lib/server.ts holds the @timbl/client calls and maps CMS entries to serializable DTOs at the boundary.
See also
- Quick Start: install and run timbl itself
- Client SDK: the method surface this example exercises
- Astro Kitchen Sink: the same surface in Astro
- Next.js Kitchen Sink: the same surface in Next.js App Router