From c32ad6351a929783fcddbdebde3d6964ba8783ea Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Wed, 4 Mar 2026 20:06:52 -0400
Subject: [PATCH 01/11] generated readmes
---
templates/angular-ts/README.md | 117 +++++++++++++++
templates/basic-cpp/README.md | 118 +++++++++++++++
templates/basic-cs/README.md | 117 +++++++++++++++
templates/basic-rs/README.md | 112 +++++++++++++--
templates/basic-ts/README.md | 107 ++++++++++++++
templates/browser-ts/README.md | 106 ++++++++++++++
templates/bun-ts/README.md | 232 ++++++++++++++++++++++++++++++
templates/deno-ts/README.md | 245 ++++++++++++++++++++++++++++++++
templates/nextjs-ts/README.md | 195 +++++++++++++++++++++++++
templates/nodejs-ts/README.md | 199 ++++++++++++++++++++++++++
templates/nuxt-ts/README.md | 227 +++++++++++++++++++++++++++++
templates/react-ts/README.md | 116 +++++++++++++++
templates/remix-ts/README.md | 190 +++++++++++++++++++++++++
templates/svelte-ts/README.md | 114 +++++++++++++++
templates/tanstack-ts/README.md | 144 +++++++++++++++++++
templates/vue-ts/README.md | 114 +++++++++++++++
16 files changed, 2441 insertions(+), 12 deletions(-)
create mode 100644 templates/angular-ts/README.md
create mode 100644 templates/basic-cpp/README.md
create mode 100644 templates/basic-cs/README.md
create mode 100644 templates/basic-ts/README.md
create mode 100644 templates/browser-ts/README.md
create mode 100644 templates/bun-ts/README.md
create mode 100644 templates/deno-ts/README.md
create mode 100644 templates/nextjs-ts/README.md
create mode 100644 templates/nodejs-ts/README.md
create mode 100644 templates/nuxt-ts/README.md
create mode 100644 templates/react-ts/README.md
create mode 100644 templates/remix-ts/README.md
create mode 100644 templates/svelte-ts/README.md
create mode 100644 templates/tanstack-ts/README.md
create mode 100644 templates/vue-ts/README.md
diff --git a/templates/angular-ts/README.md b/templates/angular-ts/README.md
new file mode 100644
index 00000000000..de5dbae93cd
--- /dev/null
+++ b/templates/angular-ts/README.md
@@ -0,0 +1,117 @@
+Get a SpacetimeDB Angular app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Angular client.
+
+This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Angular development server.
+
+```bash
+spacetime dev --template angular-ts
+```
+
+
+
+## Open your app
+
+Navigate to [http://localhost:4200](http://localhost:4200) to see your app running.
+
+The template includes a basic Angular app connected to SpacetimeDB.
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/app/app.component.ts` to build your UI.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── src/ # Angular frontend
+│ └── app/
+│ ├── app.component.ts
+│ ├── app.config.ts
+│ └── module_bindings/ # Auto-generated types
+├── angular.json
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+## Next steps
+
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/basic-cpp/README.md b/templates/basic-cpp/README.md
new file mode 100644
index 00000000000..8f67916a74e
--- /dev/null
+++ b/templates/basic-cpp/README.md
@@ -0,0 +1,118 @@
+Get a SpacetimeDB C++ app running in under 5 minutes.
+
+## Prerequisites
+
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+- [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) 4.0.21+ installed
+- CMake 3.20+ and a make/ninja backend
+- C++20 toolchain (host) — build targets WASM via Emscripten
+
+After installing the SDK, run the appropriate `emsdk_env` script (PowerShell or Bash) so `emcc` and the CMake toolchain file are available on `PATH`.
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Install Emscripten
+
+Use the official SDK (see [Emscripten downloads](https://emscripten.org/docs/getting_started/downloads.html)) and activate the environment so `emcc` and the CMake toolchain file are on PATH. We recommend Emscripten 4.0.21+.
+
+```bash
+# From your emsdk directory (after downloading/cloning)
+# Windows PowerShell
+./emsdk install 4.0.21
+./emsdk activate 4.0.21
+./emsdk_env.ps1
+
+# macOS/Linux
+./emsdk install 4.0.21
+./emsdk activate 4.0.21
+source ./emsdk_env.sh
+```
+
+
+
+## Create your project
+
+Use the CLI-managed workflow with `spacetime build`, which wraps CMake + `emcc` for you, starts the local server, builds/publishes your module, and generates client bindings.
+
+```bash
+spacetime dev --template basic-cpp
+```
+
+
+Need manual control? You can still drive CMake+emcc directly (see `spacetimedb/CMakeLists.txt`), but the recommended path is `spacetime build`/`spacetime dev`.
+
+
+
+
+
+Server code lives in the `spacetimedb` folder; the template uses CMake and the SpacetimeDB C++ SDK.
+
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your C++ module
+│ ├── CMakeLists.txt
+│ └── src/
+│ └── lib.cpp # Server-side logic
+├── Cargo.toml
+└── src/
+└── main.rs # Rust client application
+```
+
+
+
+## Understand tables and reducers
+
+The template includes a `Person` table and two reducers: `add` to insert, `say_hello` to iterate and log.
+
+```cpp
+#include "spacetimedb.h"
+using namespace SpacetimeDB;
+
+struct Person { std::string name; };
+SPACETIMEDB_STRUCT(Person, name)
+SPACETIMEDB_TABLE(Person, person, Public)
+
+SPACETIMEDB_REDUCER(add, ReducerContext ctx, std::string name) {
+ctx.db[person].insert(Person{name});
+return Ok();
+}
+
+SPACETIMEDB_REDUCER(say_hello, ReducerContext ctx) {
+for (const auto& person : ctx.db[person]) {
+LOG_INFO("Hello, " + person.name + "!");
+}
+LOG_INFO("Hello, World!");
+return Ok();
+}
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then call reducers and inspect data right from the CLI.
+
+```bash
+cd my-spacetime-app
+
+# Insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+
+# Call say_hello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+```
+
+## Notes
+
+- To use a local SDK clone instead of the fetched archive, set `SPACETIMEDB_CPP_SDK_DIR` before running `spacetime dev`/`spacetime build`.
+- The template builds to WebAssembly with exceptions disabled (`-fno-exceptions`).
+- If `emcc` is not found, re-run the appropriate `emsdk_env` script to populate environment variables.
diff --git a/templates/basic-cs/README.md b/templates/basic-cs/README.md
new file mode 100644
index 00000000000..547dd74da37
--- /dev/null
+++ b/templates/basic-cs/README.md
@@ -0,0 +1,117 @@
+Get a SpacetimeDB C# app running in under 5 minutes.
+
+## Prerequisites
+
+- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Install .NET WASI workload
+
+SpacetimeDB C# modules compile to WebAssembly using the WASI experimental workload.
+
+```bash
+dotnet workload install wasi-experimental
+```
+
+
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a C# SpacetimeDB module.
+
+This will start the local SpacetimeDB server, compile and publish your module, and generate C# client bindings.
+
+```bash
+spacetime dev --template basic-cs
+```
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/Lib.cs` to add tables and reducers. Use the generated bindings in the client project.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ ├── StdbModule.csproj
+│ └── Lib.cs # Server-side logic
+├── client.csproj
+├── Program.cs # Client application
+└── module_bindings/ # Auto-generated types
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/Lib.cs` to see the module code. The template includes a `Person` table and two reducers: `Add` to insert a person, and `SayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```csharp
+using SpacetimeDB;
+
+public static partial class Module
+{
+[SpacetimeDB.Table(Accessor = "Person", Public = true)]
+public partial struct Person
+{
+public string Name;
+}
+
+[SpacetimeDB.Reducer]
+public static void Add(ReducerContext ctx, string name)
+{
+ctx.Db.Person.Insert(new Person { Name = name });
+}
+
+[SpacetimeDB.Reducer]
+public static void SayHello(ReducerContext ctx)
+{
+foreach (var person in ctx.Db.Person.Iter())
+{
+Log.Info($"Hello, {person.Name}!");
+}
+Log.Info("Hello, World!");
+}
+}
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM Person"
+name
+---------
+"Alice"
+
+# Call say_hello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [C# SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/csharp-reference) for detailed API docs
diff --git a/templates/basic-rs/README.md b/templates/basic-rs/README.md
index 968af7be799..550c3e4f19c 100644
--- a/templates/basic-rs/README.md
+++ b/templates/basic-rs/README.md
@@ -1,15 +1,103 @@
-# SpacetimeDB Rust Client
+Get a SpacetimeDB Rust app running in under 5 minutes.
-A basic Rust client for SpacetimeDB.
+## Prerequisites
-## Setup
+- [Rust](https://www.rust-lang.org/tools/install) installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
-1. Build and publish your server module
-2. Generate bindings:
- ```
- spacetime generate --lang rust --out-dir src/module_bindings
- ```
-3. Run the client:
- ```
- cargo run
- ```
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a Rust SpacetimeDB module.
+
+This will start the local SpacetimeDB server, compile and publish your module, and generate Rust client bindings.
+
+```bash
+spacetime dev --template basic-rs
+```
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/lib.rs` to add tables and reducers. Use the generated bindings in `src/module_bindings/` to build your client.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ ├── Cargo.toml
+│ └── src/
+│ └── lib.rs # Server-side logic
+├── Cargo.toml
+├── src/
+│ ├── main.rs # Client application
+│ └── module_bindings/ # Auto-generated types
+└── README.md
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/lib.rs` to see the module code. The template includes a `Person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```rust
+use spacetimedb::{ReducerContext, Table};
+
+#[spacetimedb::table(accessor = person, public)]
+pub struct Person {
+name: String,
+}
+
+#[spacetimedb::reducer]
+pub fn add(ctx: &ReducerContext, name: String) {
+ctx.db.person().insert(Person { name });
+}
+
+#[spacetimedb::reducer]
+pub fn say_hello(ctx: &ReducerContext) {
+for person in ctx.db.person().iter() {
+log::info!("Hello, {}!", person.name);
+}
+log::info!("Hello, World!");
+}
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call say_hello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [Rust SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/rust-reference) for detailed API docs
diff --git a/templates/basic-ts/README.md b/templates/basic-ts/README.md
new file mode 100644
index 00000000000..29c3a8cce07
--- /dev/null
+++ b/templates/basic-ts/README.md
@@ -0,0 +1,107 @@
+Get a SpacetimeDB TypeScript app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a TypeScript SpacetimeDB module.
+
+This will start the local SpacetimeDB server, publish your module, and generate TypeScript client bindings.
+
+```bash
+spacetime dev --template basic-ts
+```
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Use the generated bindings in `src/module_bindings/` to build your client.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── src/
+│ ├── main.ts # Client application
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/browser-ts/README.md b/templates/browser-ts/README.md
new file mode 100644
index 00000000000..41eaa2d87d6
--- /dev/null
+++ b/templates/browser-ts/README.md
@@ -0,0 +1,106 @@
+Get a SpacetimeDB app running in the browser with inline JavaScript.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a TypeScript SpacetimeDB module.
+
+This will start the local SpacetimeDB server, publish your module, and generate TypeScript client bindings.
+
+```bash
+spacetime dev --template browser-ts
+```
+
+
+
+## Build the client bindings
+
+The generated TypeScript bindings need to be bundled into a JavaScript file that can be loaded in the browser via a script tag.
+
+```bash
+cd my-spacetime-app
+npm install
+npm run build
+```
+
+
+
+## Open in browser
+
+Open `index.html` directly in your browser. The app connects to SpacetimeDB and displays data in real-time.
+
+The JavaScript code runs inline in a script tag, using the bundled `DbConnection` class.
+
+:::tip
+The browser IIFE bundle also exposes the generated `tables` query builders, so you can use query-builder subscriptions here too.
+:::
+
+```html
+
+
+
+
+```
+
+
+
+## Call reducers
+
+Reducers are functions that modify data — they're the only way to write to the database.
+
+```javascript
+// Call a reducer with named arguments
+conn.reducers.add({ name: 'Alice' });
+```
+
+
+
+## React to changes
+
+Register callbacks to update your UI when data changes.
+
+```javascript
+conn.db.person.onInsert((ctx, person) => {
+console.log('New person:', person.name);
+});
+
+conn.db.person.onDelete((ctx, person) => {
+console.log('Removed:', person.name);
+});
+```
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/bun-ts/README.md b/templates/bun-ts/README.md
new file mode 100644
index 00000000000..a04dc8f337b
--- /dev/null
+++ b/templates/bun-ts/README.md
@@ -0,0 +1,232 @@
+Get a SpacetimeDB Bun app running in under 5 minutes.
+
+## Prerequisites
+
+- [Bun](https://bun.sh/) installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Bun client.
+
+This will start the local SpacetimeDB server, publish your module, and generate TypeScript bindings.
+
+```bash
+spacetime dev --template bun-ts
+```
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/main.ts` to build your Bun client.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── src/
+│ ├── main.ts # Bun client script
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Run the client
+
+In a new terminal, run the Bun client. It will connect to SpacetimeDB and start an interactive CLI where you can add people and query the database.
+
+```bash
+# Run with auto-reload during development
+bun run dev
+
+# Or run once
+
+bun run start
+
+```
+
+
+
+## Use the interactive CLI
+
+The client provides a command-line interface to interact with your SpacetimeDB module. Type a name to add a person, or use the built-in commands.
+
+```
+
+Connecting to SpacetimeDB...
+URI: ws://localhost:3000
+Module: bun-ts
+
+Connected to SpacetimeDB!
+Identity: abc123def456...
+
+Current people (0):
+(none yet)
+
+Commands:
+ - Add a person with that name
+list - Show all people
+hello - Greet everyone (check server logs)
+Ctrl+C - Quit
+
+> Alice
+> [Added] Alice
+
+> Bob
+> [Added] Bob
+
+> list
+> People in database:
+
+- Alice
+- Bob
+
+> hello
+> Called sayHello reducer (check server logs)
+
+````
+
+
+
+## Understand the client code
+
+Open `src/main.ts` to see the Bun client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and sets up the interactive CLI using Bun's native APIs.
+
+Unlike browser apps, Bun stores the authentication token in a file using `Bun.file()` and `Bun.write()`.
+
+```typescript
+import { DbConnection } from './module_bindings/index.js';
+
+// Build and establish connection
+DbConnection.builder()
+.withUri(HOST)
+.withDatabaseName(DB_NAME)
+.withToken(await loadToken()) // Load saved token from file
+.onConnect((conn, identity, token) => {
+console.log('Connected! Identity:', identity.toHexString());
+saveToken(token); // Save token for future connections
+
+// Subscribe to all tables
+conn.subscriptionBuilder()
+.onApplied((ctx) => {
+// Show current data, start CLI
+setupCLI(conn);
+})
+.subscribeToAllTables();
+
+// Listen for table changes
+conn.db.person.onInsert((ctx, person) => {
+console.log(`[Added] ${person.name}`);
+});
+})
+.build();
+````
+
+
+
+## Test with the SpacetimeDB CLI
+
+You can also use the SpacetimeDB CLI to call reducers and query your data directly. Changes made via the CLI will appear in your Bun client in real-time.
+
+```bash
+# Call the add reducer to insert a person
+spacetime call add Charlie
+
+# Query the person table
+
+spacetime sql "SELECT \* FROM person"
+name
+
+---
+
+"Alice"
+"Bob"
+"Charlie"
+
+# Call sayHello to greet everyone
+
+spacetime call say_hello
+
+# View the module logs
+
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, Bob!
+2025-01-13T12:00:00.000000Z INFO: Hello, Charlie!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+
+````
+
+
+
+## Bun-specific features
+
+**Native WebSocket:** Bun has built-in WebSocket support, so no additional packages like `undici` are needed.
+
+**Built-in TypeScript:** Bun runs TypeScript directly without transpilation, making startup faster and eliminating the need for `tsx` or `ts-node`.
+
+**Environment variables:** Bun automatically loads `.env` files. Configure the connection using `SPACETIMEDB_HOST` and `SPACETIMEDB_DB_NAME` environment variables.
+
+**File APIs:** The template uses `Bun.file()` and `Bun.write()` for token persistence, which are faster than Node.js `fs` operations.
+
+```bash
+# Configure via environment variables
+SPACETIMEDB_HOST=ws://localhost:3000 \
+SPACETIMEDB_DB_NAME=my-app \
+bun run start
+
+# Or create a .env file (Bun loads it automatically)
+echo "SPACETIMEDB_HOST=ws://localhost:3000" > .env
+echo "SPACETIMEDB_DB_NAME=my-app" >> .env
+bun run start
+````
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/deno-ts/README.md b/templates/deno-ts/README.md
new file mode 100644
index 00000000000..716e0eb5a70
--- /dev/null
+++ b/templates/deno-ts/README.md
@@ -0,0 +1,245 @@
+Get a SpacetimeDB Deno app running in under 5 minutes.
+
+## Prerequisites
+
+- [Deno](https://deno.land/) installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Deno client.
+
+This will start the local SpacetimeDB server, publish your module, and generate TypeScript bindings.
+
+```bash
+spacetime dev --template deno-ts
+```
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/main.ts` to build your Deno client.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── src/
+│ ├── main.ts # Deno client script
+│ └── module_bindings/ # Auto-generated types
+└── package.json # Scripts and dependencies (dev, start, spacetime:generate)
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Run the client
+
+In a new terminal, run the Deno client. It will connect to SpacetimeDB and start an interactive CLI where you can add people and query the database.
+
+```bash
+# Run with auto-reload during development
+pnpm run dev
+# or: npm run dev
+
+# Or run once
+pnpm run start
+# or: npm run start
+```
+
+
+
+## Use the interactive CLI
+
+The client provides a command-line interface to interact with your SpacetimeDB module. Type a name to add a person, or use the built-in commands.
+
+```
+
+Connecting to SpacetimeDB...
+URI: ws://localhost:3000
+Module: deno-ts
+
+Connected to SpacetimeDB!
+Identity: abc123def456...
+
+Current people (0):
+(none yet)
+
+Commands:
+ - Add a person with that name
+list - Show all people
+hello - Greet everyone (check server logs)
+Ctrl+C - Quit
+
+> Alice
+> [Added] Alice
+
+> Bob
+> [Added] Bob
+
+> list
+> People in database:
+
+- Alice
+- Bob
+
+> hello
+> Called sayHello reducer (check server logs)
+
+````
+
+
+
+## Understand the client code
+
+Open `src/main.ts` to see the Deno client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and sets up the interactive CLI using Deno's native APIs.
+
+Unlike browser apps, Deno stores the authentication token in a file using `Deno.readTextFile()` and `Deno.writeTextFile()`.
+
+```typescript
+import { DbConnection } from './module_bindings/index.ts';
+
+// Build and establish connection
+DbConnection.builder()
+.withUri(HOST)
+.withDatabaseName(DB_NAME)
+.withToken(await loadToken()) // Load saved token from file
+.onConnect((conn, identity, token) => {
+console.log('Connected! Identity:', identity.toHexString());
+saveToken(token); // Save token for future connections
+
+// Subscribe to all tables
+conn.subscriptionBuilder()
+.onApplied((ctx) => {
+// Show current data, start CLI
+setupCLI(conn);
+})
+.subscribeToAllTables();
+
+// Listen for table changes
+conn.db.person.onInsert((ctx, person) => {
+console.log(`[Added] ${person.name}`);
+});
+})
+.build();
+````
+
+
+
+## Test with the SpacetimeDB CLI
+
+You can also use the SpacetimeDB CLI to call reducers and query your data directly. Changes made via the CLI will appear in your Deno client in real-time.
+
+```bash
+# Call the add reducer to insert a person
+spacetime call add Charlie
+
+# Query the person table
+
+spacetime sql "SELECT \* FROM person"
+name
+
+---
+
+"Alice"
+"Bob"
+"Charlie"
+
+# Call sayHello to greet everyone
+
+spacetime call say_hello
+
+# View the module logs
+
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, Bob!
+2025-01-13T12:00:00.000000Z INFO: Hello, Charlie!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+
+````
+
+
+
+## Deno-specific features
+
+**Permissions:** Deno requires explicit permissions. The template uses `--allow-net` for WebSocket connections, `--allow-read` and `--allow-write` for token persistence, and `--allow-env` for configuration.
+
+**Sloppy imports:** The `--unstable-sloppy-imports` flag is required because the generated module bindings use extensionless imports (Node.js convention), while Deno requires explicit file extensions. This flag enables Node.js-style module resolution.
+
+**Built-in TypeScript:** Deno runs TypeScript directly without transpilation, making startup faster and eliminating the need for build tools.
+
+**Dependencies:** The `package.json` file declares the `spacetimedb` dependency; Deno resolves it from there (and from `node_modules` after `pnpm install` when developing in the SpacetimeDB repo).
+
+**node_modules:** When using the template from the repo workspace, run `pnpm install` so `spacetimedb` is linked. For a project created with `spacetime init`, Deno resolves the versioned dependency from package.json.
+
+```bash
+# Configure via environment variables
+SPACETIMEDB_HOST=ws://localhost:3000 \
+SPACETIMEDB_DB_NAME=my-app \
+pnpm run start
+
+# Or run with explicit permissions
+deno run --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts
+
+# package.json defines scripts and the spacetimedb dependency
+cat package.json
+{
+"scripts": {
+"dev": "deno run --watch --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts",
+"start": "deno run --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts",
+"spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb"
+},
+"dependencies": {
+"spacetimedb": "workspace:*"
+}
+}
+````
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/nextjs-ts/README.md b/templates/nextjs-ts/README.md
new file mode 100644
index 00000000000..25223dd1625
--- /dev/null
+++ b/templates/nextjs-ts/README.md
@@ -0,0 +1,195 @@
+Get a SpacetimeDB Next.js app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Next.js client.
+
+This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Next.js development server.
+
+```bash
+spacetime dev --template nextjs-ts
+```
+
+
+
+## Open your app
+
+Navigate to [http://localhost:3000](http://localhost:3000) to see your app running.
+
+The `spacetime dev` command automatically configures your app to connect to SpacetimeDB via environment variables in `.env.local`.
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code using the Next.js App Router.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `app/page.tsx` and `app/PersonList.tsx` to build your UI.
+
+```
+my-nextjs-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # SpacetimeDB module logic
+├── app/ # Next.js App Router
+│ ├── layout.tsx # Root layout with providers
+│ ├── page.tsx # Server Component (fetches initial data)
+│ ├── PersonList.tsx # Client Component (real-time updates)
+│ └── providers.tsx # SpacetimeDB provider for real-time
+├── lib/
+│ └── spacetimedb-server.ts # Server-side data fetching
+├── src/
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+
+
+## Understand server-side rendering
+
+The SpacetimeDB SDK works both server-side and client-side. The template uses a hybrid approach:
+
+- **Server Component** (`page.tsx`): Fetches initial data during SSR for fast page loads
+- **Client Component** (`PersonList.tsx`): Maintains a real-time WebSocket connection for live updates
+
+The `lib/spacetimedb-server.ts` file provides a utility for server-side data fetching.
+
+```tsx
+// lib/spacetimedb-server.ts
+import { DbConnection, tables } from '../src/module_bindings';
+
+export async function fetchPeople() {
+return new Promise((resolve, reject) => {
+const connection = DbConnection.builder()
+.withUri(process.env.SPACETIMEDB_HOST!)
+.withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
+.onConnect(conn => {
+conn.subscriptionBuilder()
+.onApplied(() => {
+const people = Array.from(conn.db.person.iter());
+conn.disconnect();
+resolve(people);
+})
+.subscribe(tables.person);
+})
+.build();
+});
+}
+```
+
+
+
+## Use React hooks for real-time data
+
+In client components, use `useTable` to subscribe to table data and `useReducer` to call reducers. The Server Component passes initial data as props for instant rendering.
+
+```tsx
+// app/page.tsx (Server Component)
+import { PersonList } from './PersonList';
+import { fetchPeople } from '../lib/spacetimedb-server';
+
+export default async function Home() {
+const initialPeople = await fetchPeople();
+return ;
+}
+```
+
+```tsx
+// app/PersonList.tsx (Client Component)
+'use client';
+
+import { tables, reducers } from '../src/module_bindings';
+import { useTable, useReducer } from 'spacetimedb/react';
+
+export function PersonList({ initialPeople }) {
+// Real-time data from WebSocket subscription
+const [people, isLoading] = useTable(tables.person);
+const addPerson = useReducer(reducers.add);
+
+// Use server data until client is connected
+const displayPeople = isLoading ? initialPeople : people;
+
+return (
+
+{displayPeople.map((person, i) => - {person.name}
)}
+
+);
+}
+```
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/nodejs-ts/README.md b/templates/nodejs-ts/README.md
new file mode 100644
index 00000000000..940ea021392
--- /dev/null
+++ b/templates/nodejs-ts/README.md
@@ -0,0 +1,199 @@
+Get a SpacetimeDB Node.js app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Node.js client.
+
+This starts the local SpacetimeDB server, publishes your module, generates TypeScript bindings, and runs the Node.js client.
+
+```bash
+spacetime dev --template nodejs-ts
+```
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/main.ts` to build your Node.js client.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── src/
+│ ├── main.ts # Node.js client script
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Run the client
+
+`spacetime dev` starts both the server and the Node.js client. The client connects to SpacetimeDB, subscribes to tables, and displays people as they are added or removed. Press Ctrl+C to exit.
+
+```bash
+spacetime dev --template nodejs-ts
+```
+
+
+
+## Call reducers from the SpacetimeDB CLI
+
+Use the SpacetimeDB CLI to add people and invoke reducers. Changes appear in your Node.js client in real time.
+
+```bash
+# Add a person
+spacetime call add Alice
+spacetime call add Bob
+
+# Greet everyone (check server logs)
+
+spacetime call say_hello
+
+# Query the database
+
+spacetime sql "SELECT * FROM person"
+
+````
+
+
+
+## Understand the client code
+
+Open `src/main.ts` to see the Node.js client. It uses `DbConnection.builder()` to connect to SpacetimeDB, subscribes to tables, and registers callbacks for insert/delete events. Unlike browser apps, Node.js stores the authentication token in a file instead of localStorage.
+
+```typescript
+import { DbConnection } from './module_bindings/index.js';
+
+DbConnection.builder()
+.withUri(HOST)
+.withDatabaseName(DB_NAME)
+.withToken(loadToken()) // Load saved token from file
+.onConnect((conn, identity, token) => {
+console.log('Connected! Identity:', identity.toHexString());
+saveToken(token); // Save token for future connections
+
+// Subscribe to all tables
+conn.subscriptionBuilder()
+.onApplied((ctx) => {
+// Show current people
+const people = [...ctx.db.person.iter()];
+console.log('Current people:', people.length);
+})
+.subscribeToAllTables();
+
+// Listen for table changes
+conn.db.person.onInsert((ctx, person) => {
+console.log(`[Added] ${person.name}`);
+});
+})
+.build();
+````
+
+
+
+## More CLI examples
+
+The SpacetimeDB CLI can call reducers and query your data. Changes appear in your Node.js client in real time.
+
+```bash
+# Call the add reducer to insert a person
+spacetime call add Charlie
+
+# Query the person table
+
+spacetime sql "SELECT * FROM person"
+name
+
+---
+
+"Alice"
+"Bob"
+"Charlie"
+
+# Call sayHello to greet everyone
+
+spacetime call say_hello
+
+# View the module logs
+
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, Bob!
+2025-01-13T12:00:00.000000Z INFO: Hello, Charlie!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+
+````
+
+
+
+## Node.js considerations
+
+**WebSocket support:** Node.js 22+ has native WebSocket support. For Node.js 18-21, the SDK automatically uses the `undici` package (included in devDependencies).
+
+**Environment variables:** Configure the connection using `SPACETIMEDB_HOST` and `SPACETIMEDB_DB_NAME` environment variables.
+
+**Exiting:** Press Ctrl+C to stop the client.
+
+```bash
+# Configure via environment variables
+SPACETIMEDB_HOST=ws://localhost:3000 \
+SPACETIMEDB_DB_NAME=my-app \
+npm run start
+
+# Or use a .env file with dotenv
+````
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/nuxt-ts/README.md b/templates/nuxt-ts/README.md
new file mode 100644
index 00000000000..2ffe36a6804
--- /dev/null
+++ b/templates/nuxt-ts/README.md
@@ -0,0 +1,227 @@
+Get a SpacetimeDB Nuxt app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Nuxt client.
+
+This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Nuxt development server.
+
+```bash
+spacetime dev --template nuxt-ts
+```
+
+
+
+## Open your app
+
+Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
+
+The template includes a basic Nuxt app connected to SpacetimeDB.
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `components/AppContent.vue` to build your UI, and `app.vue` to configure the SpacetimeDB connection.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # SpacetimeDB module logic
+├── app.vue # Root component with provider
+├── components/
+│ └── AppContent.vue # Main UI component
+├── server/
+│ └── api/
+│ └── people.get.ts # Server-side data fetching
+├── module_bindings/ # Auto-generated types
+├── nuxt.config.ts # Nuxt configuration
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+
+
+## Understand server-side rendering
+
+The SpacetimeDB SDK works both server-side and client-side. The template uses a hybrid approach:
+
+- **Server API route** (`server/api/people.get.ts`): Fetches initial data during SSR for fast page loads
+- **Client composables**: Maintain a real-time WebSocket connection for live updates
+
+The server API route connects to SpacetimeDB, subscribes, fetches data, and disconnects.
+
+```typescript
+// server/api/people.get.ts
+import { DbConnection, tables } from '../../module_bindings';
+
+export default defineEventHandler(async () => {
+return new Promise((resolve, reject) => {
+DbConnection.builder()
+.withUri(process.env.SPACETIMEDB_HOST!)
+.withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
+.onConnect((conn) => {
+conn.subscriptionBuilder()
+.onApplied(() => {
+const people = Array.from(conn.db.person.iter());
+conn.disconnect();
+resolve(people);
+})
+.subscribe(tables.person);
+})
+.build();
+});
+});
+```
+
+
+
+## Set up the SpacetimeDB provider
+
+The root `app.vue` wraps your app in a `SpacetimeDBProvider` that manages the WebSocket connection. The provider is wrapped in `ClientOnly` so it only runs in the browser, while SSR uses the server API route for initial data.
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## Use composables and SSR data together
+
+Use `useFetch` to load initial data server-side, then Vue composables for real-time updates on the client. The component displays server-fetched data immediately while the WebSocket connection establishes.
+
+```vue
+
+
+```
+
+## Next steps
+
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/react-ts/README.md b/templates/react-ts/README.md
new file mode 100644
index 00000000000..cd4c7488af3
--- /dev/null
+++ b/templates/react-ts/README.md
@@ -0,0 +1,116 @@
+Get a SpacetimeDB React app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and React client.
+
+This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the React development server.
+
+```bash
+spacetime dev --template react-ts
+```
+
+
+
+## Open your app
+
+Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
+
+The template includes a basic React app connected to SpacetimeDB.
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `client/src/App.tsx` to build your UI.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── client/ # React frontend
+│ └── src/
+│ ├── App.tsx
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/remix-ts/README.md b/templates/remix-ts/README.md
new file mode 100644
index 00000000000..19ce625b7ef
--- /dev/null
+++ b/templates/remix-ts/README.md
@@ -0,0 +1,190 @@
+Get a SpacetimeDB Remix app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Remix client.
+
+This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Remix development server.
+
+```bash
+spacetime dev --template remix-ts
+```
+
+
+
+## Open your app
+
+Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
+
+The `spacetime dev` command automatically configures your app to connect to SpacetimeDB via environment variables.
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code using Remix with Vite.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `app/routes/_index.tsx` to build your UI.
+
+```
+my-remix-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # SpacetimeDB module logic
+├── app/ # Remix app
+│ ├── root.tsx # Root layout with SpacetimeDB provider
+│ ├── lib/
+│ │ └── spacetimedb.server.ts # Server-side data fetching
+│ └── routes/
+│ └── _index.tsx # Home page with loader
+├── src/
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+
+
+## Understand server-side rendering
+
+The SpacetimeDB SDK works both server-side and client-side. The template uses Remix loaders for SSR:
+
+- **Loader**: Fetches initial data from SpacetimeDB during server rendering
+- **Client**: Maintains a real-time WebSocket connection for live updates
+
+The `app/lib/spacetimedb.server.ts` file provides a utility for server-side data fetching.
+
+```tsx
+// app/lib/spacetimedb.server.ts
+import { DbConnection, tables } from '../../src/module_bindings';
+
+export async function fetchPeople() {
+return new Promise((resolve, reject) => {
+const connection = DbConnection.builder()
+.withUri(process.env.SPACETIMEDB_HOST!)
+.withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
+.onConnect(conn => {
+conn.subscriptionBuilder()
+.onApplied(() => {
+const people = Array.from(conn.db.person.iter());
+conn.disconnect();
+resolve(people);
+})
+.subscribe(tables.person);
+})
+.build();
+});
+}
+```
+
+
+
+## Use loaders and hooks for data
+
+Use Remix loaders to fetch initial data server-side, then React hooks for real-time updates. The loader data is passed to the component and displayed immediately while the client connects.
+
+```tsx
+// app/routes/_index.tsx
+import { useLoaderData } from '@remix-run/react';
+import { tables, reducers } from '../../src/module_bindings';
+import { useTable, useReducer } from 'spacetimedb/react';
+import { fetchPeople } from '../lib/spacetimedb.server';
+
+export async function loader() {
+const people = await fetchPeople();
+return { initialPeople: people };
+}
+
+export default function Index() {
+const { initialPeople } = useLoaderData();
+
+// Real-time data from WebSocket subscription
+const [people, isLoading] = useTable(tables.person);
+const addPerson = useReducer(reducers.add);
+
+// Use server data until client is connected
+const displayPeople = isLoading ? initialPeople : people;
+
+return (
+
+{displayPeople.map((person, i) => - {person.name}
)}
+
+);
+}
+```
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/svelte-ts/README.md b/templates/svelte-ts/README.md
new file mode 100644
index 00000000000..75c5994dc7d
--- /dev/null
+++ b/templates/svelte-ts/README.md
@@ -0,0 +1,114 @@
+Get a SpacetimeDB Svelte app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Svelte client.
+
+This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Svelte development server.
+
+```bash
+spacetime dev --template svelte-ts
+```
+
+
+
+## Open your app
+
+Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
+
+The template includes a basic Svelte app connected to SpacetimeDB.
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/App.svelte` to build your UI.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── src/ # Svelte frontend
+│ ├── App.svelte
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+## Next steps
+
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/tanstack-ts/README.md b/templates/tanstack-ts/README.md
new file mode 100644
index 00000000000..13ee03ed0ca
--- /dev/null
+++ b/templates/tanstack-ts/README.md
@@ -0,0 +1,144 @@
+Get a SpacetimeDB app with TanStack Start running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and TanStack Start.
+
+This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the development server.
+
+```bash
+spacetime dev --template tanstack-ts
+```
+
+
+
+## Open your app
+
+Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
+
+The template includes a TanStack Start app with TanStack Query integration with SpacetimeDB.
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/routes/index.tsx` to build your UI.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── src/ # TanStack Start frontend
+│ ├── router.tsx # QueryClient + SpacetimeDB setup
+│ ├── routes/
+│ │ ├── __root.tsx # Root layout
+│ │ └── index.tsx # Main app component
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+
+
+## Query and update data
+
+Use `useSpacetimeDBQuery()` to subscribe to tables with TanStack Query — it returns `[data, loading, query]`. SpacetimeDB React hooks also work with TanStack Start.
+
+```typescript
+import { useSpacetimeDBQuery, useReducer } from 'spacetimedb/tanstack';
+import { tables, reducers } from '../module_bindings';
+
+function App() {
+const [people, loading] = useSpacetimeDBQuery(tables.person);
+const addPerson = useReducer(reducers.add);
+
+if (loading) return Loading...
;
+
+return (
+
+{people.map((person, i) => (
+- {person.name}
+))}
+
+);
+}
+````
+
+## Next steps
+
+- See the [Chat App Tutorial](https://spacetimedb.com/docs/intro/tutorials/chat-app) for a complete example
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
diff --git a/templates/vue-ts/README.md b/templates/vue-ts/README.md
new file mode 100644
index 00000000000..4f4e41fe915
--- /dev/null
+++ b/templates/vue-ts/README.md
@@ -0,0 +1,114 @@
+Get a SpacetimeDB Vue app running in under 5 minutes.
+
+## Prerequisites
+
+- [Node.js](https://nodejs.org/) 18+ installed
+- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
+
+Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.
+
+---
+
+## Create your project
+
+Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Vue client.
+
+This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Vue development server.
+
+```bash
+spacetime dev --template vue-ts
+```
+
+
+
+## Open your app
+
+Navigate to [http://localhost:5173](http://localhost:5173) to see your app running.
+
+The template includes a basic Vue app connected to SpacetimeDB.
+
+
+
+## Explore the project structure
+
+Your project contains both server and client code.
+
+Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/App.vue` to build your UI.
+
+```
+my-spacetime-app/
+├── spacetimedb/ # Your SpacetimeDB module
+│ └── src/
+│ └── index.ts # Server-side logic
+├── src/ # Vue frontend
+│ ├── App.vue
+│ └── module_bindings/ # Auto-generated types
+└── package.json
+```
+
+
+
+## Understand tables and reducers
+
+Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `sayHello` to greet everyone.
+
+Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
+
+```typescript
+import { schema, table, t } from 'spacetimedb/server';
+
+const spacetimedb = schema({
+person: table(
+{ public: true },
+{
+name: t.string(),
+}
+),
+});
+export default spacetimedb;
+
+export const add = spacetimedb.reducer(
+{ name: t.string() },
+(ctx, { name }) => {
+ctx.db.person.insert({ name });
+}
+);
+
+export const sayHello = spacetimedb.reducer(ctx => {
+for (const person of ctx.db.person.iter()) {
+console.info(`Hello, ${person.name}!`);
+}
+console.info('Hello, World!');
+});
+```
+
+
+
+## Test with the CLI
+
+Open a new terminal and navigate to your project directory. Then use the SpacetimeDB CLI to call reducers and query your data directly.
+
+```bash
+cd my-spacetime-app
+
+# Call the add reducer to insert a person
+spacetime call add Alice
+
+# Query the person table
+spacetime sql "SELECT * FROM person"
+name
+---------
+"Alice"
+
+# Call sayHello to greet everyone
+spacetime call say_hello
+
+# View the module logs
+spacetime logs
+2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
+2025-01-13T12:00:00.000000Z INFO: Hello, World!
+```
+
+## Next steps
+
+- Read the [TypeScript SDK Reference](https://spacetimedb.com/docs/intro/core-concepts/clients/typescript-reference) for detailed API docs
From 9119caf292524a8b3f932df1ec8d303305c59172 Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Wed, 4 Mar 2026 20:22:24 -0400
Subject: [PATCH 02/11] templates and readmes
---
templates/angular-ts/.template.json | 6 +++++-
templates/basic-cpp/.template.json | 5 ++++-
templates/basic-cs/.template.json | 5 ++++-
templates/basic-rs/.template.json | 5 ++++-
templates/basic-ts/.template.json | 5 ++++-
templates/browser-ts/.template.json | 5 ++++-
templates/bun-ts/.template.json | 6 +++++-
templates/chat-console-cs/.template.json | 5 ++++-
templates/chat-console-rs/.template.json | 5 ++++-
templates/chat-react-ts/.template.json | 6 +++++-
templates/deno-ts/.template.json | 6 +++++-
templates/nextjs-ts/.template.json | 6 +++++-
templates/nodejs-ts/.template.json | 6 +++++-
templates/nuxt-ts/.template.json | 6 +++++-
templates/react-ts/.template.json | 6 +++++-
templates/remix-ts/.template.json | 6 +++++-
templates/svelte-ts/.template.json | 6 +++++-
templates/tanstack-ts/.template.json | 6 +++++-
templates/vue-ts/.template.json | 6 +++++-
19 files changed, 88 insertions(+), 19 deletions(-)
diff --git a/templates/angular-ts/.template.json b/templates/angular-ts/.template.json
index afc15dbf0e5..d5f08cb3eb1 100644
--- a/templates/angular-ts/.template.json
+++ b/templates/angular-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Angular web app with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "angular",
+ "spacetimedb"
+ ]
}
diff --git a/templates/basic-cpp/.template.json b/templates/basic-cpp/.template.json
index 983a1b065ff..2d23d5e36e8 100644
--- a/templates/basic-cpp/.template.json
+++ b/templates/basic-cpp/.template.json
@@ -1,5 +1,8 @@
{
"description": "A basic C++ server template with only stubs for code",
"server_lang": "cpp",
- "client_lang": "rust"
+ "client_lang": "rust",
+ "builtWith": [
+ "spacetimedb"
+ ]
}
diff --git a/templates/basic-cs/.template.json b/templates/basic-cs/.template.json
index b85112b2e07..49a481812ad 100644
--- a/templates/basic-cs/.template.json
+++ b/templates/basic-cs/.template.json
@@ -1,5 +1,8 @@
{
"description": "A basic C# client and server template with only stubs for code",
"client_lang": "csharp",
- "server_lang": "csharp"
+ "server_lang": "csharp",
+ "builtWith": [
+ "spacetimedb"
+ ]
}
diff --git a/templates/basic-rs/.template.json b/templates/basic-rs/.template.json
index 85ebfd88dc7..c2d67cfba37 100644
--- a/templates/basic-rs/.template.json
+++ b/templates/basic-rs/.template.json
@@ -1,5 +1,8 @@
{
"description": "A basic Rust client and server template with only stubs for code",
"client_lang": "rust",
- "server_lang": "rust"
+ "server_lang": "rust",
+ "builtWith": [
+ "spacetimedb"
+ ]
}
diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json
index f81df6ce52c..63349429477 100644
--- a/templates/basic-ts/.template.json
+++ b/templates/basic-ts/.template.json
@@ -1,5 +1,8 @@
{
"description": "A basic TypeScript client and server template with only stubs for code",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "spacetimedb"
+ ]
}
diff --git a/templates/browser-ts/.template.json b/templates/browser-ts/.template.json
index 8cc8855138b..c2150d05574 100644
--- a/templates/browser-ts/.template.json
+++ b/templates/browser-ts/.template.json
@@ -1,5 +1,8 @@
{
"description": "Browser web app with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "spacetimedb"
+ ]
}
diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json
index f6a2ab3b67e..78a924e32ab 100644
--- a/templates/bun-ts/.template.json
+++ b/templates/bun-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Bun TypeScript client and server template",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "bun",
+ "spacetimedb"
+ ]
}
diff --git a/templates/chat-console-cs/.template.json b/templates/chat-console-cs/.template.json
index 0a476ac93f0..5a88a797792 100644
--- a/templates/chat-console-cs/.template.json
+++ b/templates/chat-console-cs/.template.json
@@ -1,5 +1,8 @@
{
"description": "C# server/client implementing quickstart chat",
"client_lang": "csharp",
- "server_lang": "csharp"
+ "server_lang": "csharp",
+ "builtWith": [
+ "spacetimedb"
+ ]
}
diff --git a/templates/chat-console-rs/.template.json b/templates/chat-console-rs/.template.json
index 07ad0e5291a..0cfd462f950 100644
--- a/templates/chat-console-rs/.template.json
+++ b/templates/chat-console-rs/.template.json
@@ -1,5 +1,8 @@
{
"description": "Rust server/client implementing quickstart chat",
"client_lang": "rust",
- "server_lang": "rust"
+ "server_lang": "rust",
+ "builtWith": [
+ "spacetimedb"
+ ]
}
diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json
index c4a20fefa4a..83088d82eed 100644
--- a/templates/chat-react-ts/.template.json
+++ b/templates/chat-react-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "TypeScript server/client implementing quickstart chat",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "react",
+ "spacetimedb"
+ ]
}
diff --git a/templates/deno-ts/.template.json b/templates/deno-ts/.template.json
index 9b815e8c464..2ad7af9aa73 100644
--- a/templates/deno-ts/.template.json
+++ b/templates/deno-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Deno TypeScript client and server template",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "deno",
+ "spacetimedb"
+ ]
}
diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json
index 19b5fb62be2..6814434698d 100644
--- a/templates/nextjs-ts/.template.json
+++ b/templates/nextjs-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Next.js App Router with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "nextjs",
+ "spacetimedb"
+ ]
}
diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json
index af38af2c866..616d1be4172 100644
--- a/templates/nodejs-ts/.template.json
+++ b/templates/nodejs-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Node.js TypeScript client and server template",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "nodejs",
+ "spacetimedb"
+ ]
}
diff --git a/templates/nuxt-ts/.template.json b/templates/nuxt-ts/.template.json
index 812ba61c574..31444e9dbdd 100644
--- a/templates/nuxt-ts/.template.json
+++ b/templates/nuxt-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Nuxt web app with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "nuxt",
+ "spacetimedb"
+ ]
}
diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json
index a11db218e11..de8fe80a051 100644
--- a/templates/react-ts/.template.json
+++ b/templates/react-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "React web app with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "react",
+ "spacetimedb"
+ ]
}
diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json
index 924245a1bc9..16a2f1eadfd 100644
--- a/templates/remix-ts/.template.json
+++ b/templates/remix-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Remix with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "remix",
+ "spacetimedb"
+ ]
}
diff --git a/templates/svelte-ts/.template.json b/templates/svelte-ts/.template.json
index 2f7d4081e89..434e3eede24 100644
--- a/templates/svelte-ts/.template.json
+++ b/templates/svelte-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Svelte web app with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "svelte",
+ "spacetimedb"
+ ]
}
diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json
index d8cc9052acb..19fb5c2334d 100644
--- a/templates/tanstack-ts/.template.json
+++ b/templates/tanstack-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "TanStack Start (React + TanStack Query/Router) with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "tanstack",
+ "spacetimedb"
+ ]
}
diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json
index ea14a0674ad..8213c071a08 100644
--- a/templates/vue-ts/.template.json
+++ b/templates/vue-ts/.template.json
@@ -1,5 +1,9 @@
{
"description": "Vue.js web app with TypeScript server",
"client_lang": "typescript",
- "server_lang": "typescript"
+ "server_lang": "typescript",
+ "builtWith": [
+ "vue",
+ "spacetimedb"
+ ]
}
From 17b0834b8055d3158405623f8698213af946ea4e Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Thu, 5 Mar 2026 10:11:27 -0400
Subject: [PATCH 03/11] readmes + scripts to generate
---
templates/angular-ts/README.md | 32 +-
templates/basic-cpp/README.md | 32 +-
templates/basic-cs/README.md | 46 +--
templates/basic-rs/README.md | 16 +-
templates/basic-ts/README.md | 32 +-
templates/browser-ts/README.md | 48 +--
templates/bun-ts/README.md | 70 ++--
templates/deno-ts/README.md | 86 ++---
templates/nextjs-ts/README.md | 90 ++---
templates/nodejs-ts/README.md | 72 ++--
templates/nuxt-ts/README.md | 112 +++----
templates/react-ts/README.md | 32 +-
templates/remix-ts/README.md | 88 ++---
templates/svelte-ts/README.md | 32 +-
templates/tanstack-ts/README.md | 56 ++--
templates/vue-ts/README.md | 32 +-
tools/templates/README.md | 29 ++
tools/templates/generate-template-readmes.ts | 180 ++++++++++
tools/templates/package.json | 14 +
tools/templates/pnpm-lock.yaml | 332 +++++++++++++++++++
tools/templates/update-template-jsons.ts | 97 ++++++
21 files changed, 1090 insertions(+), 438 deletions(-)
create mode 100644 tools/templates/README.md
create mode 100644 tools/templates/generate-template-readmes.ts
create mode 100644 tools/templates/package.json
create mode 100644 tools/templates/pnpm-lock.yaml
create mode 100644 tools/templates/update-template-jsons.ts
diff --git a/templates/angular-ts/README.md b/templates/angular-ts/README.md
index de5dbae93cd..2dcdcc7241c 100644
--- a/templates/angular-ts/README.md
+++ b/templates/angular-ts/README.md
@@ -61,27 +61,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -99,9 +99,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
diff --git a/templates/basic-cpp/README.md b/templates/basic-cpp/README.md
index 8f67916a74e..4e122dc0e94 100644
--- a/templates/basic-cpp/README.md
+++ b/templates/basic-cpp/README.md
@@ -39,17 +39,17 @@ Use the CLI-managed workflow with `spacetime build`, which wraps CMake + `emcc`
```bash
spacetime dev --template basic-cpp
```
-
-
+
+
Need manual control? You can still drive CMake+emcc directly (see `spacetimedb/CMakeLists.txt`), but the recommended path is `spacetime build`/`spacetime dev`.
-
-
-
-
+
+
+
+
Server code lives in the `spacetimedb` folder; the template uses CMake and the SpacetimeDB C++ SDK.
-
-
+
+
```
my-spacetime-app/
├── spacetimedb/ # Your C++ module
@@ -58,7 +58,7 @@ my-spacetime-app/
│ └── lib.cpp # Server-side logic
├── Cargo.toml
└── src/
-└── main.rs # Rust client application
+ └── main.rs # Rust client application
```
@@ -76,16 +76,16 @@ SPACETIMEDB_STRUCT(Person, name)
SPACETIMEDB_TABLE(Person, person, Public)
SPACETIMEDB_REDUCER(add, ReducerContext ctx, std::string name) {
-ctx.db[person].insert(Person{name});
-return Ok();
+ ctx.db[person].insert(Person{name});
+ return Ok();
}
SPACETIMEDB_REDUCER(say_hello, ReducerContext ctx) {
-for (const auto& person : ctx.db[person]) {
-LOG_INFO("Hello, " + person.name + "!");
-}
-LOG_INFO("Hello, World!");
-return Ok();
+ for (const auto& person : ctx.db[person]) {
+ LOG_INFO("Hello, " + person.name + "!");
+ }
+ LOG_INFO("Hello, World!");
+ return Ok();
}
```
diff --git a/templates/basic-cs/README.md b/templates/basic-cs/README.md
index 547dd74da37..d693ce55ad2 100644
--- a/templates/basic-cs/README.md
+++ b/templates/basic-cs/README.md
@@ -60,27 +60,27 @@ using SpacetimeDB;
public static partial class Module
{
-[SpacetimeDB.Table(Accessor = "Person", Public = true)]
-public partial struct Person
-{
-public string Name;
-}
-
-[SpacetimeDB.Reducer]
-public static void Add(ReducerContext ctx, string name)
-{
-ctx.Db.Person.Insert(new Person { Name = name });
-}
-
-[SpacetimeDB.Reducer]
-public static void SayHello(ReducerContext ctx)
-{
-foreach (var person in ctx.Db.Person.Iter())
-{
-Log.Info($"Hello, {person.Name}!");
-}
-Log.Info("Hello, World!");
-}
+ [SpacetimeDB.Table(Accessor = "Person", Public = true)]
+ public partial struct Person
+ {
+ public string Name;
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void Add(ReducerContext ctx, string name)
+ {
+ ctx.Db.Person.Insert(new Person { Name = name });
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void SayHello(ReducerContext ctx)
+ {
+ foreach (var person in ctx.Db.Person.Iter())
+ {
+ Log.Info($"Hello, {person.Name}!");
+ }
+ Log.Info("Hello, World!");
+ }
}
```
@@ -98,9 +98,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM Person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call say_hello to greet everyone
spacetime call say_hello
diff --git a/templates/basic-rs/README.md b/templates/basic-rs/README.md
index 550c3e4f19c..083102976ec 100644
--- a/templates/basic-rs/README.md
+++ b/templates/basic-rs/README.md
@@ -53,20 +53,20 @@ use spacetimedb::{ReducerContext, Table};
#[spacetimedb::table(accessor = person, public)]
pub struct Person {
-name: String,
+ name: String,
}
#[spacetimedb::reducer]
pub fn add(ctx: &ReducerContext, name: String) {
-ctx.db.person().insert(Person { name });
+ ctx.db.person().insert(Person { name });
}
#[spacetimedb::reducer]
pub fn say_hello(ctx: &ReducerContext) {
-for person in ctx.db.person().iter() {
-log::info!("Hello, {}!", person.name);
-}
-log::info!("Hello, World!");
+ for person in ctx.db.person().iter() {
+ log::info!("Hello, {}!", person.name);
+ }
+ log::info!("Hello, World!");
}
```
@@ -84,9 +84,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call say_hello to greet everyone
spacetime call say_hello
diff --git a/templates/basic-ts/README.md b/templates/basic-ts/README.md
index 29c3a8cce07..a90b1813f62 100644
--- a/templates/basic-ts/README.md
+++ b/templates/basic-ts/README.md
@@ -50,27 +50,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -88,9 +88,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
diff --git a/templates/browser-ts/README.md b/templates/browser-ts/README.md
index 41eaa2d87d6..7e69847d072 100644
--- a/templates/browser-ts/README.md
+++ b/templates/browser-ts/README.md
@@ -48,28 +48,28 @@ The browser IIFE bundle also exposes the generated `tables` query builders, so y
```
@@ -92,11 +92,11 @@ Register callbacks to update your UI when data changes.
```javascript
conn.db.person.onInsert((ctx, person) => {
-console.log('New person:', person.name);
+ console.log('New person:', person.name);
});
conn.db.person.onDelete((ctx, person) => {
-console.log('Removed:', person.name);
+ console.log('Removed:', person.name);
});
```
diff --git a/templates/bun-ts/README.md b/templates/bun-ts/README.md
index a04dc8f337b..21e5e33ef17 100644
--- a/templates/bun-ts/README.md
+++ b/templates/bun-ts/README.md
@@ -50,27 +50,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -144,27 +144,27 @@ import { DbConnection } from './module_bindings/index.js';
// Build and establish connection
DbConnection.builder()
-.withUri(HOST)
-.withDatabaseName(DB_NAME)
-.withToken(await loadToken()) // Load saved token from file
-.onConnect((conn, identity, token) => {
-console.log('Connected! Identity:', identity.toHexString());
-saveToken(token); // Save token for future connections
-
-// Subscribe to all tables
-conn.subscriptionBuilder()
-.onApplied((ctx) => {
-// Show current data, start CLI
-setupCLI(conn);
-})
-.subscribeToAllTables();
-
-// Listen for table changes
-conn.db.person.onInsert((ctx, person) => {
-console.log(`[Added] ${person.name}`);
-});
-})
-.build();
+ .withUri(HOST)
+ .withDatabaseName(DB_NAME)
+ .withToken(await loadToken()) // Load saved token from file
+ .onConnect((conn, identity, token) => {
+ console.log('Connected! Identity:', identity.toHexString());
+ saveToken(token); // Save token for future connections
+
+ // Subscribe to all tables
+ conn.subscriptionBuilder()
+ .onApplied((ctx) => {
+ // Show current data, start CLI
+ setupCLI(conn);
+ })
+ .subscribeToAllTables();
+
+ // Listen for table changes
+ conn.db.person.onInsert((ctx, person) => {
+ console.log(`[Added] ${person.name}`);
+ });
+ })
+ .build();
````
diff --git a/templates/deno-ts/README.md b/templates/deno-ts/README.md
index 716e0eb5a70..3b72e75af58 100644
--- a/templates/deno-ts/README.md
+++ b/templates/deno-ts/README.md
@@ -50,27 +50,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -144,27 +144,27 @@ import { DbConnection } from './module_bindings/index.ts';
// Build and establish connection
DbConnection.builder()
-.withUri(HOST)
-.withDatabaseName(DB_NAME)
-.withToken(await loadToken()) // Load saved token from file
-.onConnect((conn, identity, token) => {
-console.log('Connected! Identity:', identity.toHexString());
-saveToken(token); // Save token for future connections
-
-// Subscribe to all tables
-conn.subscriptionBuilder()
-.onApplied((ctx) => {
-// Show current data, start CLI
-setupCLI(conn);
-})
-.subscribeToAllTables();
-
-// Listen for table changes
-conn.db.person.onInsert((ctx, person) => {
-console.log(`[Added] ${person.name}`);
-});
-})
-.build();
+ .withUri(HOST)
+ .withDatabaseName(DB_NAME)
+ .withToken(await loadToken()) // Load saved token from file
+ .onConnect((conn, identity, token) => {
+ console.log('Connected! Identity:', identity.toHexString());
+ saveToken(token); // Save token for future connections
+
+ // Subscribe to all tables
+ conn.subscriptionBuilder()
+ .onApplied((ctx) => {
+ // Show current data, start CLI
+ setupCLI(conn);
+ })
+ .subscribeToAllTables();
+
+ // Listen for table changes
+ conn.db.person.onInsert((ctx, person) => {
+ console.log(`[Added] ${person.name}`);
+ });
+ })
+ .build();
````
@@ -228,14 +228,14 @@ deno run --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-im
# package.json defines scripts and the spacetimedb dependency
cat package.json
{
-"scripts": {
-"dev": "deno run --watch --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts",
-"start": "deno run --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts",
-"spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb"
-},
-"dependencies": {
-"spacetimedb": "workspace:*"
-}
+ "scripts": {
+ "dev": "deno run --watch --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts",
+ "start": "deno run --allow-net --allow-read --allow-write --allow-env --unstable-sloppy-imports src/main.ts",
+ "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb"
+ },
+ "dependencies": {
+ "spacetimedb": "workspace:*"
+ }
}
````
diff --git a/templates/nextjs-ts/README.md b/templates/nextjs-ts/README.md
index 25223dd1625..9fc4e60cca9 100644
--- a/templates/nextjs-ts/README.md
+++ b/templates/nextjs-ts/README.md
@@ -64,27 +64,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -102,9 +102,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
@@ -131,21 +131,21 @@ The `lib/spacetimedb-server.ts` file provides a utility for server-side data fet
import { DbConnection, tables } from '../src/module_bindings';
export async function fetchPeople() {
-return new Promise((resolve, reject) => {
-const connection = DbConnection.builder()
-.withUri(process.env.SPACETIMEDB_HOST!)
-.withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
-.onConnect(conn => {
-conn.subscriptionBuilder()
-.onApplied(() => {
-const people = Array.from(conn.db.person.iter());
-conn.disconnect();
-resolve(people);
-})
-.subscribe(tables.person);
-})
-.build();
-});
+ return new Promise((resolve, reject) => {
+ const connection = DbConnection.builder()
+ .withUri(process.env.SPACETIMEDB_HOST!)
+ .withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
+ .onConnect(conn => {
+ conn.subscriptionBuilder()
+ .onApplied(() => {
+ const people = Array.from(conn.db.person.iter());
+ conn.disconnect();
+ resolve(people);
+ })
+ .subscribe(tables.person);
+ })
+ .build();
+ });
}
```
@@ -161,8 +161,8 @@ import { PersonList } from './PersonList';
import { fetchPeople } from '../lib/spacetimedb-server';
export default async function Home() {
-const initialPeople = await fetchPeople();
-return ;
+ const initialPeople = await fetchPeople();
+ return ;
}
```
@@ -174,18 +174,18 @@ import { tables, reducers } from '../src/module_bindings';
import { useTable, useReducer } from 'spacetimedb/react';
export function PersonList({ initialPeople }) {
-// Real-time data from WebSocket subscription
-const [people, isLoading] = useTable(tables.person);
-const addPerson = useReducer(reducers.add);
-
-// Use server data until client is connected
-const displayPeople = isLoading ? initialPeople : people;
-
-return (
-
-{displayPeople.map((person, i) => - {person.name}
)}
-
-);
+ // Real-time data from WebSocket subscription
+ const [people, isLoading] = useTable(tables.person);
+ const addPerson = useReducer(reducers.add);
+
+ // Use server data until client is connected
+ const displayPeople = isLoading ? initialPeople : people;
+
+ return (
+
+ {displayPeople.map((person, i) => - {person.name}
)}
+
+ );
}
```
diff --git a/templates/nodejs-ts/README.md b/templates/nodejs-ts/README.md
index 940ea021392..66a81387d26 100644
--- a/templates/nodejs-ts/README.md
+++ b/templates/nodejs-ts/README.md
@@ -50,27 +50,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -115,28 +115,28 @@ Open `src/main.ts` to see the Node.js client. It uses `DbConnection.builder()` t
import { DbConnection } from './module_bindings/index.js';
DbConnection.builder()
-.withUri(HOST)
-.withDatabaseName(DB_NAME)
-.withToken(loadToken()) // Load saved token from file
-.onConnect((conn, identity, token) => {
-console.log('Connected! Identity:', identity.toHexString());
-saveToken(token); // Save token for future connections
-
-// Subscribe to all tables
-conn.subscriptionBuilder()
-.onApplied((ctx) => {
-// Show current people
-const people = [...ctx.db.person.iter()];
-console.log('Current people:', people.length);
-})
-.subscribeToAllTables();
-
-// Listen for table changes
-conn.db.person.onInsert((ctx, person) => {
-console.log(`[Added] ${person.name}`);
-});
-})
-.build();
+ .withUri(HOST)
+ .withDatabaseName(DB_NAME)
+ .withToken(loadToken()) // Load saved token from file
+ .onConnect((conn, identity, token) => {
+ console.log('Connected! Identity:', identity.toHexString());
+ saveToken(token); // Save token for future connections
+
+ // Subscribe to all tables
+ conn.subscriptionBuilder()
+ .onApplied((ctx) => {
+ // Show current people
+ const people = [...ctx.db.person.iter()];
+ console.log('Current people:', people.length);
+ })
+ .subscribeToAllTables();
+
+ // Listen for table changes
+ conn.db.person.onInsert((ctx, person) => {
+ console.log(`[Added] ${person.name}`);
+ });
+ })
+ .build();
````
diff --git a/templates/nuxt-ts/README.md b/templates/nuxt-ts/README.md
index 2ffe36a6804..7df666f1010 100644
--- a/templates/nuxt-ts/README.md
+++ b/templates/nuxt-ts/README.md
@@ -63,27 +63,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -101,9 +101,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
@@ -130,21 +130,21 @@ The server API route connects to SpacetimeDB, subscribes, fetches data, and disc
import { DbConnection, tables } from '../../module_bindings';
export default defineEventHandler(async () => {
-return new Promise((resolve, reject) => {
-DbConnection.builder()
-.withUri(process.env.SPACETIMEDB_HOST!)
-.withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
-.onConnect((conn) => {
-conn.subscriptionBuilder()
-.onApplied(() => {
-const people = Array.from(conn.db.person.iter());
-conn.disconnect();
-resolve(people);
-})
-.subscribe(tables.person);
-})
-.build();
-});
+ return new Promise((resolve, reject) => {
+ DbConnection.builder()
+ .withUri(process.env.SPACETIMEDB_HOST!)
+ .withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
+ .onConnect((conn) => {
+ conn.subscriptionBuilder()
+ .onApplied(() => {
+ const people = Array.from(conn.db.person.iter());
+ conn.disconnect();
+ resolve(people);
+ })
+ .subscribe(tables.person);
+ })
+ .build();
+ });
});
```
@@ -157,14 +157,14 @@ The root `app.vue` wraps your app in a `SpacetimeDBProvider` that manages the We
```vue
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
```
@@ -208,16 +208,16 @@ const { data: initialPeople } = await useFetch('/api/people');
// On the client, use real-time composables
let conn, people, addReducer;
if (import.meta.client) {
-const { useSpacetimeDB, useTable, useReducer } = await import('spacetimedb/vue');
-conn = useSpacetimeDB();
-[people] = useTable(tables.person);
-addReducer = useReducer(reducers.add);
+ const { useSpacetimeDB, useTable, useReducer } = await import('spacetimedb/vue');
+ conn = useSpacetimeDB();
+ [people] = useTable(tables.person);
+ addReducer = useReducer(reducers.add);
}
// Use real-time data once connected, fall back to SSR data
const displayPeople = computed(() => {
-if (conn?.isActive && people?.value) return people.value;
-return initialPeople.value ?? [];
+ if (conn?.isActive && people?.value) return people.value;
+ return initialPeople.value ?? [];
});
```
diff --git a/templates/react-ts/README.md b/templates/react-ts/README.md
index cd4c7488af3..8db4516126e 100644
--- a/templates/react-ts/README.md
+++ b/templates/react-ts/README.md
@@ -59,27 +59,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -97,9 +97,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
diff --git a/templates/remix-ts/README.md b/templates/remix-ts/README.md
index 19ce625b7ef..7a5c0504866 100644
--- a/templates/remix-ts/README.md
+++ b/templates/remix-ts/README.md
@@ -63,27 +63,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -101,9 +101,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
@@ -130,21 +130,21 @@ The `app/lib/spacetimedb.server.ts` file provides a utility for server-side data
import { DbConnection, tables } from '../../src/module_bindings';
export async function fetchPeople() {
-return new Promise((resolve, reject) => {
-const connection = DbConnection.builder()
-.withUri(process.env.SPACETIMEDB_HOST!)
-.withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
-.onConnect(conn => {
-conn.subscriptionBuilder()
-.onApplied(() => {
-const people = Array.from(conn.db.person.iter());
-conn.disconnect();
-resolve(people);
-})
-.subscribe(tables.person);
-})
-.build();
-});
+ return new Promise((resolve, reject) => {
+ const connection = DbConnection.builder()
+ .withUri(process.env.SPACETIMEDB_HOST!)
+ .withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
+ .onConnect(conn => {
+ conn.subscriptionBuilder()
+ .onApplied(() => {
+ const people = Array.from(conn.db.person.iter());
+ conn.disconnect();
+ resolve(people);
+ })
+ .subscribe(tables.person);
+ })
+ .build();
+ });
}
```
@@ -162,25 +162,25 @@ import { useTable, useReducer } from 'spacetimedb/react';
import { fetchPeople } from '../lib/spacetimedb.server';
export async function loader() {
-const people = await fetchPeople();
-return { initialPeople: people };
+ const people = await fetchPeople();
+ return { initialPeople: people };
}
export default function Index() {
-const { initialPeople } = useLoaderData();
+ const { initialPeople } = useLoaderData();
-// Real-time data from WebSocket subscription
-const [people, isLoading] = useTable(tables.person);
-const addPerson = useReducer(reducers.add);
+ // Real-time data from WebSocket subscription
+ const [people, isLoading] = useTable(tables.person);
+ const addPerson = useReducer(reducers.add);
-// Use server data until client is connected
-const displayPeople = isLoading ? initialPeople : people;
+ // Use server data until client is connected
+ const displayPeople = isLoading ? initialPeople : people;
-return (
-
-{displayPeople.map((person, i) => - {person.name}
)}
-
-);
+ return (
+
+ {displayPeople.map((person, i) => - {person.name}
)}
+
+ );
}
```
diff --git a/templates/svelte-ts/README.md b/templates/svelte-ts/README.md
index 75c5994dc7d..3ef596dd66e 100644
--- a/templates/svelte-ts/README.md
+++ b/templates/svelte-ts/README.md
@@ -58,27 +58,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -96,9 +96,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
diff --git a/templates/tanstack-ts/README.md b/templates/tanstack-ts/README.md
index 13ee03ed0ca..090e5bf244e 100644
--- a/templates/tanstack-ts/README.md
+++ b/templates/tanstack-ts/README.md
@@ -61,27 +61,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -99,9 +99,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
@@ -123,18 +123,18 @@ import { useSpacetimeDBQuery, useReducer } from 'spacetimedb/tanstack';
import { tables, reducers } from '../module_bindings';
function App() {
-const [people, loading] = useSpacetimeDBQuery(tables.person);
-const addPerson = useReducer(reducers.add);
-
-if (loading) return Loading...
;
-
-return (
-
-{people.map((person, i) => (
-- {person.name}
-))}
-
-);
+ const [people, loading] = useSpacetimeDBQuery(tables.person);
+ const addPerson = useReducer(reducers.add);
+
+ if (loading) return Loading...
;
+
+ return (
+
+ {people.map((person, i) => (
+ - {person.name}
+ ))}
+
+ );
}
````
diff --git a/templates/vue-ts/README.md b/templates/vue-ts/README.md
index 4f4e41fe915..95e5b54e398 100644
--- a/templates/vue-ts/README.md
+++ b/templates/vue-ts/README.md
@@ -58,27 +58,27 @@ Tables store your data. Reducers are functions that modify data — they're the
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
-person: table(
-{ public: true },
-{
-name: t.string(),
-}
-),
+ person: table(
+ { public: true },
+ {
+ name: t.string(),
+ }
+ ),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
-{ name: t.string() },
-(ctx, { name }) => {
-ctx.db.person.insert({ name });
-}
+ { name: t.string() },
+ (ctx, { name }) => {
+ ctx.db.person.insert({ name });
+ }
);
export const sayHello = spacetimedb.reducer(ctx => {
-for (const person of ctx.db.person.iter()) {
-console.info(`Hello, ${person.name}!`);
-}
-console.info('Hello, World!');
+ for (const person of ctx.db.person.iter()) {
+ console.info(`Hello, ${person.name}!`);
+ }
+ console.info('Hello, World!');
});
```
@@ -96,9 +96,9 @@ spacetime call add Alice
# Query the person table
spacetime sql "SELECT * FROM person"
-name
+ name
---------
-"Alice"
+ "Alice"
# Call sayHello to greet everyone
spacetime call say_hello
diff --git a/tools/templates/README.md b/tools/templates/README.md
new file mode 100644
index 00000000000..99dc2f03acc
--- /dev/null
+++ b/tools/templates/README.md
@@ -0,0 +1,29 @@
+# Template Tools
+
+Scripts for maintaining template READMEs and metadata in the SpacetimeDB repo. Output is consumed by [spacetimedb.com](https://github.com/clockworklabs/spacetime-web) for the templates page.
+
+## Scripts
+
+- **generate-readmes** – Converts quickstart MDX docs to Markdown and writes `templates//README.md`
+- **update-jsons** – Updates `builtWith` in each `templates//.template.json` from the slug
+- **generate** – Runs both (readmes first, then jsons)
+
+## Usage
+
+From this directory:
+
+```bash
+pnpm install
+pnpm run generate
+```
+
+Or individually:
+
+```bash
+pnpm run generate-readmes
+pnpm run update-jsons
+```
+
+## When to run
+
+Run after changing quickstart docs (`docs/docs/00100-intro/00200-quickstarts/`) or adding/renaming templates. Commit the generated READMEs and updated `.template.json` files.
diff --git a/tools/templates/generate-template-readmes.ts b/tools/templates/generate-template-readmes.ts
new file mode 100644
index 00000000000..608e0aa5b92
--- /dev/null
+++ b/tools/templates/generate-template-readmes.ts
@@ -0,0 +1,180 @@
+/**
+ * Reads SpacetimeDB quickstart MDX docs, converts them to plain Markdown,
+ * and writes README.md into each template folder. These READMEs are consumed
+ * by spacetimedb.com's process-templates to generate the templates page.
+ *
+ * Run from SpacetimeDB repo root. Writes to templates//README.md.
+ *
+ * Usage: pnpm run generate-readmes (from tools/templates/)
+ */
+
+import { readFile, writeFile } from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const REPO_ROOT = path.resolve(__dirname, '../..');
+const TEMPLATES_DIR = path.join(REPO_ROOT, 'templates');
+const QUICKSTARTS_DIR = path.join(REPO_ROOT, 'docs/docs/00100-intro/00200-quickstarts');
+const DOCS_ROOT = path.join(REPO_ROOT, 'docs/docs');
+
+const TEMPLATE_TO_QUICKSTART: Record = {
+ 'react-ts': '00100-react.md',
+ 'nextjs-ts': '00150-nextjs.md',
+ 'vue-ts': '00150-vue.md',
+ 'nuxt-ts': '00155-nuxt.md',
+ 'svelte-ts': '00160-svelte.md',
+ 'angular-ts': '00165-angular.md',
+ 'tanstack-ts': '00170-tanstack.md',
+ 'remix-ts': '00175-remix.md',
+ 'browser-ts': '00180-browser.md',
+ 'bun-ts': '00250-bun.md',
+ 'deno-ts': '00275-deno.md',
+ 'nodejs-ts': '00300-nodejs.md',
+ 'basic-ts': '00400-typescript.md',
+ 'basic-rs': '00500-rust.md',
+ 'basic-cs': '00600-c-sharp.md',
+ 'basic-cpp': '00700-cpp.md',
+};
+
+const DOCS_BASE = 'https://spacetimedb.com/docs';
+
+function stripFrontmatterAndImports(content: string): string {
+ let out = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
+ out = out.replace(/^import .+ from ["'][^"']*@site[^"']*["'];\r?\n/gm, '');
+ return out.trim();
+}
+
+function replaceInstallCardLink(content: string): string {
+ return content.replace(
+ //g,
+ 'Install the [SpacetimeDB CLI](https://spacetimedb.com/install) before continuing.'
+ );
+}
+
+function normalizeStepText(text: string): string {
+ return text
+ .trim()
+ .split('\n')
+ .map(line => line.replace(/^\s+/, ''))
+ .join('\n');
+}
+
+function convertStepByStepToMarkdown(content: string): string {
+ const stepRegex =
+ /\s*\s*([\s\S]*?)\s*<\/StepText>\s*(?:\s*([\s\S]*?)\s*<\/StepCode>)?\s*<\/Step>/g;
+
+ return content.replace(stepRegex, (_, title, stepText, stepCode) => {
+ const normalizedText = normalizeStepText(stepText);
+ let block = `## ${title}\n\n${normalizedText}\n\n`;
+ if (stepCode && stepCode.trim()) {
+ block += stepCode.trim() + '\n\n';
+ }
+ return block;
+ });
+}
+
+function removeStepByStepWrapper(content: string): string {
+ return content.replace(/\s*([\s\S]*?)\s*<\/StepByStep>/g, '$1');
+}
+
+function stripRemainingStepTags(content: string): string {
+ let out = content
+ .replace(/([\s\S]*?)<\/StepText>/g, '$1')
+ .replace(/([\s\S]*?)<\/StepCode>/g, '$1')
+ .replace(/]*>/g, '')
+ .replace(/<\/Step>/g, '')
+ .replace(/<\/StepCode>/g, '')
+ .replace(/<\/StepText>/g, '');
+ return out;
+}
+
+function rewriteDocLinks(
+ content: string,
+ quickstartDir: string,
+ docsRoot: string
+): string {
+ return content.replace(
+ /\[([^\]]+)\]\((\.\.\/)*(.+?\.md)(#[\w-]+)?\)/g,
+ (_, linkText, parentRefs, docPath, hash) => {
+ const relPath = (parentRefs || '') + docPath;
+ const resolved = path.resolve(quickstartDir, relPath);
+ const relativeToDocs = path.relative(docsRoot, resolved).replace(/\\/g, '/');
+ const withoutExt = relativeToDocs.replace(/\.md$/, '');
+ const slug = withoutExt
+ .split('/')
+ .map(seg => seg.replace(/^\d+-/, ''))
+ .join('/');
+ const url = `${DOCS_BASE}/${slug}${hash || ''}`;
+ return `[${linkText}](${url})`;
+ }
+ );
+}
+
+function stripLineIndent(md: string): string {
+ let inCodeBlock = false;
+ return md
+ .split('\n')
+ .map(line => {
+ if (line.startsWith('```')) {
+ inCodeBlock = !inCodeBlock;
+ return line;
+ }
+ if (inCodeBlock) return line;
+ return line.replace(/^\s+/, '');
+ })
+ .join('\n');
+}
+
+function quickstartMdxToMarkdown(
+ mdx: string,
+ quickstartDir: string,
+ docsRoot: string
+): string {
+ let md = stripFrontmatterAndImports(mdx);
+ md = replaceInstallCardLink(md);
+ md = convertStepByStepToMarkdown(md);
+ md = removeStepByStepWrapper(md);
+ md = stripRemainingStepTags(md);
+ md = stripLineIndent(md);
+ md = rewriteDocLinks(md, quickstartDir, docsRoot);
+ return md.trim() + '\n';
+}
+
+export async function generateTemplateReadmes(): Promise {
+ let generated = 0;
+ for (const [templateSlug, quickstartFile] of Object.entries(TEMPLATE_TO_QUICKSTART)) {
+ const quickstartFullPath = path.join(QUICKSTARTS_DIR, quickstartFile);
+ const readmePath = path.join(TEMPLATES_DIR, templateSlug, 'README.md');
+
+ let mdx: string;
+ try {
+ mdx = await readFile(quickstartFullPath, 'utf-8');
+ } catch (err) {
+ console.warn(`Skipping ${templateSlug}: could not read ${quickstartFile}`);
+ continue;
+ }
+
+ const md = quickstartMdxToMarkdown(
+ mdx,
+ path.dirname(quickstartFullPath),
+ DOCS_ROOT
+ );
+ await writeFile(readmePath, md);
+ console.log(`Generated README for ${templateSlug}`);
+ generated++;
+ }
+
+ console.log(`Generated ${generated} template READMEs`);
+}
+
+const isMain =
+ import.meta.url === `file://${process.argv[1]}` ||
+ fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
+
+if (isMain) {
+ generateTemplateReadmes().catch(err => {
+ console.error(err);
+ process.exit(1);
+ });
+}
diff --git a/tools/templates/package.json b/tools/templates/package.json
new file mode 100644
index 00000000000..bb82a7cad00
--- /dev/null
+++ b/tools/templates/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "templates-tools",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "generate-readmes": "tsx generate-template-readmes.ts",
+ "update-jsons": "tsx update-template-jsons.ts",
+ "generate": "pnpm run generate-readmes && pnpm run update-jsons"
+ },
+ "devDependencies": {
+ "tsx": "^4.7.0",
+ "@types/node": "^20.0.0"
+ }
+}
diff --git a/tools/templates/pnpm-lock.yaml b/tools/templates/pnpm-lock.yaml
new file mode 100644
index 00000000000..bf289c47f07
--- /dev/null
+++ b/tools/templates/pnpm-lock.yaml
@@ -0,0 +1,332 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@types/node':
+ specifier: ^20.0.0
+ version: 20.19.35
+ tsx:
+ specifier: ^4.7.0
+ version: 4.21.0
+
+packages:
+
+ '@esbuild/aix-ppc64@0.27.3':
+ resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.27.3':
+ resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.27.3':
+ resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.27.3':
+ resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.27.3':
+ resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.27.3':
+ resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.27.3':
+ resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.27.3':
+ resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.27.3':
+ resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.27.3':
+ resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.27.3':
+ resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.27.3':
+ resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.27.3':
+ resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.27.3':
+ resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.27.3':
+ resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.27.3':
+ resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.27.3':
+ resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.27.3':
+ resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.27.3':
+ resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.27.3':
+ resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.27.3':
+ resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.27.3':
+ resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.27.3':
+ resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.27.3':
+ resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.27.3':
+ resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.27.3':
+ resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@types/node@20.19.35':
+ resolution: {integrity: sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==}
+
+ esbuild@0.27.3:
+ resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ get-tsconfig@4.13.6:
+ resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ tsx@4.21.0:
+ resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+snapshots:
+
+ '@esbuild/aix-ppc64@0.27.3':
+ optional: true
+
+ '@esbuild/android-arm64@0.27.3':
+ optional: true
+
+ '@esbuild/android-arm@0.27.3':
+ optional: true
+
+ '@esbuild/android-x64@0.27.3':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.27.3':
+ optional: true
+
+ '@esbuild/darwin-x64@0.27.3':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.27.3':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.27.3':
+ optional: true
+
+ '@esbuild/linux-arm64@0.27.3':
+ optional: true
+
+ '@esbuild/linux-arm@0.27.3':
+ optional: true
+
+ '@esbuild/linux-ia32@0.27.3':
+ optional: true
+
+ '@esbuild/linux-loong64@0.27.3':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.27.3':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.27.3':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.27.3':
+ optional: true
+
+ '@esbuild/linux-s390x@0.27.3':
+ optional: true
+
+ '@esbuild/linux-x64@0.27.3':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.27.3':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.27.3':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.27.3':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.27.3':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.27.3':
+ optional: true
+
+ '@esbuild/sunos-x64@0.27.3':
+ optional: true
+
+ '@esbuild/win32-arm64@0.27.3':
+ optional: true
+
+ '@esbuild/win32-ia32@0.27.3':
+ optional: true
+
+ '@esbuild/win32-x64@0.27.3':
+ optional: true
+
+ '@types/node@20.19.35':
+ dependencies:
+ undici-types: 6.21.0
+
+ esbuild@0.27.3:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.27.3
+ '@esbuild/android-arm': 0.27.3
+ '@esbuild/android-arm64': 0.27.3
+ '@esbuild/android-x64': 0.27.3
+ '@esbuild/darwin-arm64': 0.27.3
+ '@esbuild/darwin-x64': 0.27.3
+ '@esbuild/freebsd-arm64': 0.27.3
+ '@esbuild/freebsd-x64': 0.27.3
+ '@esbuild/linux-arm': 0.27.3
+ '@esbuild/linux-arm64': 0.27.3
+ '@esbuild/linux-ia32': 0.27.3
+ '@esbuild/linux-loong64': 0.27.3
+ '@esbuild/linux-mips64el': 0.27.3
+ '@esbuild/linux-ppc64': 0.27.3
+ '@esbuild/linux-riscv64': 0.27.3
+ '@esbuild/linux-s390x': 0.27.3
+ '@esbuild/linux-x64': 0.27.3
+ '@esbuild/netbsd-arm64': 0.27.3
+ '@esbuild/netbsd-x64': 0.27.3
+ '@esbuild/openbsd-arm64': 0.27.3
+ '@esbuild/openbsd-x64': 0.27.3
+ '@esbuild/openharmony-arm64': 0.27.3
+ '@esbuild/sunos-x64': 0.27.3
+ '@esbuild/win32-arm64': 0.27.3
+ '@esbuild/win32-ia32': 0.27.3
+ '@esbuild/win32-x64': 0.27.3
+
+ fsevents@2.3.3:
+ optional: true
+
+ get-tsconfig@4.13.6:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ resolve-pkg-maps@1.0.0: {}
+
+ tsx@4.21.0:
+ dependencies:
+ esbuild: 0.27.3
+ get-tsconfig: 4.13.6
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ undici-types@6.21.0: {}
diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts
new file mode 100644
index 00000000000..1bfa34cf89e
--- /dev/null
+++ b/tools/templates/update-template-jsons.ts
@@ -0,0 +1,97 @@
+/**
+ * Updates .template.json in each template folder with builtWith derived from
+ * the slug. Run from SpacetimeDB repo root.
+ *
+ * Writes to templates//.template.json. Commit those changes to keep
+ * template metadata in sync with spacetimedb.com.
+ *
+ * Usage: pnpm run update-jsons (from tools/templates/)
+ */
+
+import { readFile, readdir, writeFile } from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const REPO_ROOT = path.resolve(__dirname, '../..');
+const TEMPLATES_DIR = path.join(REPO_ROOT, 'templates');
+
+/** Framework slugs that can be derived from template folder names. Must match spacetimedb.com BUILT_WITH keys. */
+const FRAMEWORK_SLUGS = new Set([
+ 'react', 'nextjs', 'vue', 'nuxt', 'svelte', 'angular', 'tanstack', 'remix',
+ 'browser', 'bun', 'deno', 'nodejs', 'spacetimedb', 'tailwind', 'vite',
+]);
+
+function deriveBuiltWith(slug: string): string[] {
+ const parts = slug.split('-');
+ const result = new Set();
+
+ for (const part of parts) {
+ if (FRAMEWORK_SLUGS.has(part)) {
+ result.add(part);
+ }
+ }
+
+ result.add('spacetimedb');
+ return [...result];
+}
+
+export async function updateTemplateJsons(): Promise {
+ let entries: import('node:fs').Dirent[];
+ try {
+ entries = await readdir(TEMPLATES_DIR, { withFileTypes: true });
+ } catch (err) {
+ console.warn(`Could not read templates dir: ${TEMPLATES_DIR}`, err);
+ return;
+ }
+
+ const dirs = entries
+ .filter((e): e is import('node:fs').Dirent => e.isDirectory() && !e.name.startsWith('.'))
+ .map(e => e.name);
+
+ let updated = 0;
+ for (const slug of dirs) {
+ const jsonPath = path.join(TEMPLATES_DIR, slug, '.template.json');
+ let jsonRaw: string;
+ try {
+ jsonRaw = await readFile(jsonPath, 'utf-8');
+ } catch {
+ continue;
+ }
+
+ let meta: Record;
+ try {
+ meta = JSON.parse(jsonRaw);
+ } catch {
+ console.warn(`Skipping ${slug}: invalid JSON`);
+ continue;
+ }
+
+ const builtWith = Array.isArray(meta.builtWith) && meta.builtWith.length > 0
+ ? meta.builtWith as string[]
+ : deriveBuiltWith(slug);
+
+ const { image: _image, ...rest } = meta;
+ const updatedMeta = { ...rest, builtWith };
+
+ const updatedJson = JSON.stringify(updatedMeta, null, 2) + '\n';
+ if (updatedJson !== jsonRaw) {
+ await writeFile(jsonPath, updatedJson);
+ console.log(`Updated ${slug}/.template.json`);
+ updated++;
+ }
+ }
+
+ console.log(`Updated ${updated} template JSON(s)`);
+}
+
+const isMain =
+ import.meta.url === `file://${process.argv[1]}` ||
+ fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
+
+if (isMain) {
+ updateTemplateJsons().catch(err => {
+ console.error(err);
+ process.exit(1);
+ });
+}
From 864e5d5eaa3dd018c50c91c1c0c983a089d2e261 Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Thu, 5 Mar 2026 12:30:44 -0400
Subject: [PATCH 04/11] updates
---
templates/angular-ts/.template.json | 5 +-
templates/basic-cpp/.template.json | 2 +-
templates/basic-cs/.template.json | 3 +-
templates/basic-rs/.template.json | 4 +-
templates/basic-ts/.template.json | 5 +-
templates/browser-ts/.template.json | 4 +-
templates/bun-ts/.template.json | 4 +-
templates/chat-console-cs/.template.json | 2 +-
templates/chat-console-rs/.template.json | 4 +-
templates/chat-react-ts/.template.json | 16 ++-
templates/deno-ts/.template.json | 3 +-
templates/nextjs-ts/.template.json | 8 +-
templates/nodejs-ts/.template.json | 7 +-
templates/nuxt-ts/.template.json | 4 +-
templates/react-ts/.template.json | 7 +-
templates/remix-ts/.template.json | 10 +-
templates/svelte-ts/.template.json | 6 +-
templates/tanstack-ts/.template.json | 9 +-
templates/vue-ts/.template.json | 6 +-
tools/templates/README.md | 2 +-
tools/templates/update-template-jsons.ts | 148 ++++++++++++++++++++---
21 files changed, 218 insertions(+), 41 deletions(-)
diff --git a/templates/angular-ts/.template.json b/templates/angular-ts/.template.json
index d5f08cb3eb1..01832430be1 100644
--- a/templates/angular-ts/.template.json
+++ b/templates/angular-ts/.template.json
@@ -4,6 +4,9 @@
"server_lang": "typescript",
"builtWith": [
"angular",
- "spacetimedb"
+ "rxjs",
+ "spacetimedb",
+ "tslib",
+ "typescript"
]
}
diff --git a/templates/basic-cpp/.template.json b/templates/basic-cpp/.template.json
index 2d23d5e36e8..cabcda719fe 100644
--- a/templates/basic-cpp/.template.json
+++ b/templates/basic-cpp/.template.json
@@ -3,6 +3,6 @@
"server_lang": "cpp",
"client_lang": "rust",
"builtWith": [
- "spacetimedb"
+ "spacetimedb-sdk"
]
}
diff --git a/templates/basic-cs/.template.json b/templates/basic-cs/.template.json
index 49a481812ad..3acf693d379 100644
--- a/templates/basic-cs/.template.json
+++ b/templates/basic-cs/.template.json
@@ -3,6 +3,7 @@
"client_lang": "csharp",
"server_lang": "csharp",
"builtWith": [
- "spacetimedb"
+ "SpacetimeDB.ClientSDK",
+ "SpacetimeDB.Runtime"
]
}
diff --git a/templates/basic-rs/.template.json b/templates/basic-rs/.template.json
index c2d67cfba37..2d968a5b88e 100644
--- a/templates/basic-rs/.template.json
+++ b/templates/basic-rs/.template.json
@@ -3,6 +3,8 @@
"client_lang": "rust",
"server_lang": "rust",
"builtWith": [
- "spacetimedb"
+ "log",
+ "spacetimedb",
+ "spacetimedb-sdk"
]
}
diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json
index 63349429477..744e1e3875f 100644
--- a/templates/basic-ts/.template.json
+++ b/templates/basic-ts/.template.json
@@ -3,6 +3,9 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "spacetimedb"
+ "esbuild",
+ "spacetimedb",
+ "types",
+ "typescript"
]
}
diff --git a/templates/browser-ts/.template.json b/templates/browser-ts/.template.json
index c2150d05574..975a5b5e4d0 100644
--- a/templates/browser-ts/.template.json
+++ b/templates/browser-ts/.template.json
@@ -3,6 +3,8 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "spacetimedb"
+ "spacetimedb",
+ "typescript",
+ "vite"
]
}
diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json
index 78a924e32ab..4426d3c289c 100644
--- a/templates/bun-ts/.template.json
+++ b/templates/bun-ts/.template.json
@@ -4,6 +4,8 @@
"server_lang": "typescript",
"builtWith": [
"bun",
- "spacetimedb"
+ "spacetimedb",
+ "types",
+ "typescript"
]
}
diff --git a/templates/chat-console-cs/.template.json b/templates/chat-console-cs/.template.json
index 5a88a797792..e73f0e97f61 100644
--- a/templates/chat-console-cs/.template.json
+++ b/templates/chat-console-cs/.template.json
@@ -3,6 +3,6 @@
"client_lang": "csharp",
"server_lang": "csharp",
"builtWith": [
- "spacetimedb"
+ "SpacetimeDB.Runtime"
]
}
diff --git a/templates/chat-console-rs/.template.json b/templates/chat-console-rs/.template.json
index 0cfd462f950..eb0e3bdc308 100644
--- a/templates/chat-console-rs/.template.json
+++ b/templates/chat-console-rs/.template.json
@@ -3,6 +3,8 @@
"client_lang": "rust",
"server_lang": "rust",
"builtWith": [
- "spacetimedb"
+ "log.workspace",
+ "spacetimedb",
+ "spacetimedb-sdk"
]
}
diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json
index 83088d82eed..fcdacd360ae 100644
--- a/templates/chat-react-ts/.template.json
+++ b/templates/chat-react-ts/.template.json
@@ -3,7 +3,21 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
+ "eslint",
+ "eslint-plugin-react-hooks",
+ "eslint-plugin-react-refresh",
+ "globals",
+ "jsdom",
+ "prettier",
"react",
- "spacetimedb"
+ "react-dom",
+ "spacetimedb",
+ "testing-library",
+ "types",
+ "typescript",
+ "typescript-eslint",
+ "vite",
+ "vitejs",
+ "vitest"
]
}
diff --git a/templates/deno-ts/.template.json b/templates/deno-ts/.template.json
index 2ad7af9aa73..103a46b45ad 100644
--- a/templates/deno-ts/.template.json
+++ b/templates/deno-ts/.template.json
@@ -4,6 +4,7 @@
"server_lang": "typescript",
"builtWith": [
"deno",
- "spacetimedb"
+ "spacetimedb",
+ "typescript"
]
}
diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json
index 6814434698d..c13284ad276 100644
--- a/templates/nextjs-ts/.template.json
+++ b/templates/nextjs-ts/.template.json
@@ -3,7 +3,11 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "nextjs",
- "spacetimedb"
+ "next",
+ "react",
+ "react-dom",
+ "spacetimedb",
+ "types",
+ "typescript"
]
}
diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json
index 616d1be4172..f9011801551 100644
--- a/templates/nodejs-ts/.template.json
+++ b/templates/nodejs-ts/.template.json
@@ -3,7 +3,10 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "nodejs",
- "spacetimedb"
+ "esbuild",
+ "spacetimedb",
+ "types",
+ "typescript",
+ "undici"
]
}
diff --git a/templates/nuxt-ts/.template.json b/templates/nuxt-ts/.template.json
index 31444e9dbdd..e95c0d8da64 100644
--- a/templates/nuxt-ts/.template.json
+++ b/templates/nuxt-ts/.template.json
@@ -4,6 +4,8 @@
"server_lang": "typescript",
"builtWith": [
"nuxt",
- "spacetimedb"
+ "spacetimedb",
+ "typescript",
+ "vue"
]
}
diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json
index de8fe80a051..321a2d5c653 100644
--- a/templates/react-ts/.template.json
+++ b/templates/react-ts/.template.json
@@ -4,6 +4,11 @@
"server_lang": "typescript",
"builtWith": [
"react",
- "spacetimedb"
+ "react-dom",
+ "spacetimedb",
+ "types",
+ "typescript",
+ "vite",
+ "vitejs"
]
}
diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json
index 16a2f1eadfd..438fe5b08c4 100644
--- a/templates/remix-ts/.template.json
+++ b/templates/remix-ts/.template.json
@@ -3,7 +3,13 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "remix",
- "spacetimedb"
+ "isbot",
+ "react",
+ "react-dom",
+ "remix-run",
+ "spacetimedb",
+ "types",
+ "typescript",
+ "vite"
]
}
diff --git a/templates/svelte-ts/.template.json b/templates/svelte-ts/.template.json
index 434e3eede24..aabee6e1c7b 100644
--- a/templates/svelte-ts/.template.json
+++ b/templates/svelte-ts/.template.json
@@ -3,7 +3,11 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
+ "spacetimedb",
"svelte",
- "spacetimedb"
+ "svelte-check",
+ "sveltejs",
+ "typescript",
+ "vite"
]
}
diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json
index 19fb5c2334d..581c38931fe 100644
--- a/templates/tanstack-ts/.template.json
+++ b/templates/tanstack-ts/.template.json
@@ -3,7 +3,14 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
+ "react",
+ "react-dom",
+ "spacetimedb",
"tanstack",
- "spacetimedb"
+ "types",
+ "typescript",
+ "vite",
+ "vite-tsconfig-paths",
+ "vitejs"
]
}
diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json
index 8213c071a08..3c5c05599c7 100644
--- a/templates/vue-ts/.template.json
+++ b/templates/vue-ts/.template.json
@@ -3,7 +3,11 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
+ "spacetimedb",
+ "typescript",
+ "vite",
+ "vitejs",
"vue",
- "spacetimedb"
+ "vue-tsc"
]
}
diff --git a/tools/templates/README.md b/tools/templates/README.md
index 99dc2f03acc..358ae5aebea 100644
--- a/tools/templates/README.md
+++ b/tools/templates/README.md
@@ -5,7 +5,7 @@ Scripts for maintaining template READMEs and metadata in the SpacetimeDB repo. O
## Scripts
- **generate-readmes** – Converts quickstart MDX docs to Markdown and writes `templates//README.md`
-- **update-jsons** – Updates `builtWith` in each `templates//.template.json` from the slug
+- **update-jsons** – Updates `builtWith` in each `templates//.template.json` from package.json, Cargo.toml, and .csproj manifests
- **generate** – Runs both (readmes first, then jsons)
## Usage
diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts
index 1bfa34cf89e..ba93e63b93d 100644
--- a/tools/templates/update-template-jsons.ts
+++ b/tools/templates/update-template-jsons.ts
@@ -1,6 +1,6 @@
/**
* Updates .template.json in each template folder with builtWith derived from
- * the slug. Run from SpacetimeDB repo root.
+ * package.json, Cargo.toml, and .csproj manifests. Run from SpacetimeDB repo root.
*
* Writes to templates//.template.json. Commit those changes to keep
* template metadata in sync with spacetimedb.com.
@@ -16,24 +16,137 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '../..');
const TEMPLATES_DIR = path.join(REPO_ROOT, 'templates');
-/** Framework slugs that can be derived from template folder names. Must match spacetimedb.com BUILT_WITH keys. */
-const FRAMEWORK_SLUGS = new Set([
- 'react', 'nextjs', 'vue', 'nuxt', 'svelte', 'angular', 'tanstack', 'remix',
- 'browser', 'bun', 'deno', 'nodejs', 'spacetimedb', 'tailwind', 'vite',
-]);
+const PACKAGE_REFERENCE_RE = /PackageReference\s+Include="([^"]+)"/g;
-function deriveBuiltWith(slug: string): string[] {
- const parts = slug.split('-');
- const result = new Set();
+/** Normalize npm package name: @scope/pkg → scope, else use as-is */
+function normalizeNpmPackageName(name: string): string {
+ if (name.startsWith('@')) {
+ const slash = name.indexOf('/');
+ return slash > 0 ? name.slice(1, slash) : name.slice(1);
+ }
+ return name;
+}
+
+function parsePackageJson(content: string): string[] {
+ const result: string[] = [];
+ let pkg: { dependencies?: Record; devDependencies?: Record };
+ try {
+ pkg = JSON.parse(content);
+ } catch {
+ return result;
+ }
+ for (const deps of [pkg.dependencies, pkg.devDependencies]) {
+ if (deps && typeof deps === 'object') {
+ for (const name of Object.keys(deps)) {
+ result.push(normalizeNpmPackageName(name));
+ }
+ }
+ }
+ return result;
+}
+
+/** Parse [dependencies] section from Cargo.toml. Keys only, no external deps. */
+function parseCargoToml(content: string): string[] {
+ const result: string[] = [];
+ const lines = content.split(/\r?\n/);
+ let inDependencies = false;
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (trimmed.startsWith('[')) {
+ inDependencies = trimmed === '[dependencies]';
+ continue;
+ }
+ if (inDependencies && trimmed && !trimmed.startsWith('#')) {
+ const eq = trimmed.indexOf('=');
+ if (eq > 0) {
+ const key = trimmed.slice(0, eq).trim();
+ if (key) result.push(key);
+ }
+ }
+ }
+ return result;
+}
+
+function parseCsproj(content: string): string[] {
+ const result: string[] = [];
+ for (const match of content.matchAll(PACKAGE_REFERENCE_RE)) {
+ result.push(match[1]);
+ }
+ return result;
+}
+
+async function findManifests(dir: string): Promise<{ packageJson: string[]; cargoToml: string[]; csproj: string[] }> {
+ const packageJson: string[] = [];
+ const cargoToml: string[] = [];
+ const csproj: string[] = [];
- for (const part of parts) {
- if (FRAMEWORK_SLUGS.has(part)) {
- result.add(part);
+ async function walk(currentDir: string): Promise {
+ let entries: import('node:fs').Dirent[];
+ try {
+ entries = await readdir(currentDir, { withFileTypes: true });
+ } catch {
+ return;
+ }
+ for (const entry of entries) {
+ const fullPath = path.join(currentDir, entry.name);
+ if (entry.isDirectory()) {
+ if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
+ await walk(fullPath);
+ }
+ } else if (entry.isFile()) {
+ if (entry.name === 'package.json') {
+ packageJson.push(fullPath);
+ } else if (entry.name === 'Cargo.toml') {
+ cargoToml.push(fullPath);
+ } else if (entry.name.endsWith('.csproj')) {
+ csproj.push(fullPath);
+ }
+ }
+ }
+ }
+
+ await walk(dir);
+ return { packageJson, cargoToml, csproj };
+}
+
+async function collectDepsFromManifests(templateDir: string): Promise {
+ const seen = new Set();
+ const { packageJson, cargoToml, csproj } = await findManifests(templateDir);
+
+ for (const filePath of packageJson) {
+ try {
+ const content = await readFile(filePath, 'utf-8');
+ for (const dep of parsePackageJson(content)) {
+ seen.add(dep);
+ }
+ } catch {
+ // skip
+ }
+ }
+
+ for (const filePath of cargoToml) {
+ try {
+ const content = await readFile(filePath, 'utf-8');
+ for (const dep of parseCargoToml(content)) {
+ seen.add(dep);
+ }
+ } catch {
+ // skip
+ }
+ }
+
+ for (const filePath of csproj) {
+ try {
+ const content = await readFile(filePath, 'utf-8');
+ for (const dep of parseCsproj(content)) {
+ seen.add(dep);
+ }
+ } catch {
+ // skip
}
}
- result.add('spacetimedb');
- return [...result];
+ return [...seen].sort();
}
export async function updateTemplateJsons(): Promise {
@@ -51,7 +164,8 @@ export async function updateTemplateJsons(): Promise {
let updated = 0;
for (const slug of dirs) {
- const jsonPath = path.join(TEMPLATES_DIR, slug, '.template.json');
+ const templateDir = path.join(TEMPLATES_DIR, slug);
+ const jsonPath = path.join(templateDir, '.template.json');
let jsonRaw: string;
try {
jsonRaw = await readFile(jsonPath, 'utf-8');
@@ -67,9 +181,7 @@ export async function updateTemplateJsons(): Promise {
continue;
}
- const builtWith = Array.isArray(meta.builtWith) && meta.builtWith.length > 0
- ? meta.builtWith as string[]
- : deriveBuiltWith(slug);
+ const builtWith = await collectDepsFromManifests(templateDir);
const { image: _image, ...rest } = meta;
const updatedMeta = { ...rest, builtWith };
From 637f5443bcbae65c81be88f1c9ed5bc26373a5ca Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Thu, 5 Mar 2026 13:25:06 -0400
Subject: [PATCH 05/11] templates
---
templates/basic-rs/.template.json | 4 ++--
templates/basic-ts/.template.json | 2 +-
templates/bun-ts/.template.json | 2 +-
templates/chat-console-rs/.template.json | 4 ++--
templates/chat-react-ts/.template.json | 12 ++++++------
templates/chat-react-ts/package.json | 4 ++--
templates/deno-ts/.template.json | 2 +-
templates/nodejs-ts/.template.json | 2 +-
templates/nuxt-ts/.template.json | 4 ++--
templates/nuxt-ts/package.json | 4 ++--
templates/react-ts/.template.json | 4 ++--
templates/react-ts/package.json | 4 ++--
templates/remix-ts/.template.json | 4 ++--
templates/remix-ts/package.json | 2 +-
templates/svelte-ts/.template.json | 4 ++--
templates/svelte-ts/package.json | 2 +-
templates/tanstack-ts/.template.json | 6 +++---
templates/vue-ts/.template.json | 4 ++--
templates/vue-ts/package.json | 4 ++--
tools/templates/update-template-jsons.ts | 13 +++++++++----
20 files changed, 46 insertions(+), 41 deletions(-)
diff --git a/templates/basic-rs/.template.json b/templates/basic-rs/.template.json
index 2d968a5b88e..40a60b180ca 100644
--- a/templates/basic-rs/.template.json
+++ b/templates/basic-rs/.template.json
@@ -3,8 +3,8 @@
"client_lang": "rust",
"server_lang": "rust",
"builtWith": [
- "log",
+ "spacetimedb-sdk",
"spacetimedb",
- "spacetimedb-sdk"
+ "log"
]
}
diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json
index 744e1e3875f..4d1fbbdad84 100644
--- a/templates/basic-ts/.template.json
+++ b/templates/basic-ts/.template.json
@@ -3,9 +3,9 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "esbuild",
"spacetimedb",
"types",
+ "esbuild",
"typescript"
]
}
diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json
index 4426d3c289c..1d451e11df5 100644
--- a/templates/bun-ts/.template.json
+++ b/templates/bun-ts/.template.json
@@ -3,9 +3,9 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "bun",
"spacetimedb",
"types",
+ "bun",
"typescript"
]
}
diff --git a/templates/chat-console-rs/.template.json b/templates/chat-console-rs/.template.json
index eb0e3bdc308..3965208ce95 100644
--- a/templates/chat-console-rs/.template.json
+++ b/templates/chat-console-rs/.template.json
@@ -3,8 +3,8 @@
"client_lang": "rust",
"server_lang": "rust",
"builtWith": [
- "log.workspace",
+ "spacetimedb-sdk",
"spacetimedb",
- "spacetimedb-sdk"
+ "log.workspace"
]
}
diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json
index fcdacd360ae..4249f27c1bf 100644
--- a/templates/chat-react-ts/.template.json
+++ b/templates/chat-react-ts/.template.json
@@ -3,21 +3,21 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
+ "react",
+ "react-dom",
+ "spacetimedb",
"eslint",
+ "testing-library",
+ "types",
+ "vitejs",
"eslint-plugin-react-hooks",
"eslint-plugin-react-refresh",
"globals",
"jsdom",
"prettier",
- "react",
- "react-dom",
- "spacetimedb",
- "testing-library",
- "types",
"typescript",
"typescript-eslint",
"vite",
- "vitejs",
"vitest"
]
}
diff --git a/templates/chat-react-ts/package.json b/templates/chat-react-ts/package.json
index a5a081caed2..50bba45390a 100644
--- a/templates/chat-react-ts/package.json
+++ b/templates/chat-react-ts/package.json
@@ -16,9 +16,9 @@
"spacetime:publish": "spacetime publish --module-path server --server maincloud"
},
"dependencies": {
- "spacetimedb": "workspace:*",
"react": "^18.3.1",
- "react-dom": "^18.3.1"
+ "react-dom": "^18.3.1",
+ "spacetimedb": "workspace:*"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
diff --git a/templates/deno-ts/.template.json b/templates/deno-ts/.template.json
index 103a46b45ad..49fbea31e1f 100644
--- a/templates/deno-ts/.template.json
+++ b/templates/deno-ts/.template.json
@@ -3,8 +3,8 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "deno",
"spacetimedb",
+ "deno",
"typescript"
]
}
diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json
index f9011801551..3ba16bd9954 100644
--- a/templates/nodejs-ts/.template.json
+++ b/templates/nodejs-ts/.template.json
@@ -3,9 +3,9 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "esbuild",
"spacetimedb",
"types",
+ "esbuild",
"typescript",
"undici"
]
diff --git a/templates/nuxt-ts/.template.json b/templates/nuxt-ts/.template.json
index e95c0d8da64..c480499f5fd 100644
--- a/templates/nuxt-ts/.template.json
+++ b/templates/nuxt-ts/.template.json
@@ -4,8 +4,8 @@
"server_lang": "typescript",
"builtWith": [
"nuxt",
+ "vue",
"spacetimedb",
- "typescript",
- "vue"
+ "typescript"
]
}
diff --git a/templates/nuxt-ts/package.json b/templates/nuxt-ts/package.json
index 5c220f3bcc8..f226f6b257c 100644
--- a/templates/nuxt-ts/package.json
+++ b/templates/nuxt-ts/package.json
@@ -13,9 +13,9 @@
"spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud"
},
"dependencies": {
- "spacetimedb": "workspace:*",
"nuxt": "~3.16.0",
- "vue": "^3.5.13"
+ "vue": "^3.5.13",
+ "spacetimedb": "workspace:*"
},
"devDependencies": {
"typescript": "~5.6.2"
diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json
index 321a2d5c653..21cae666fe3 100644
--- a/templates/react-ts/.template.json
+++ b/templates/react-ts/.template.json
@@ -7,8 +7,8 @@
"react-dom",
"spacetimedb",
"types",
+ "vitejs",
"typescript",
- "vite",
- "vitejs"
+ "vite"
]
}
diff --git a/templates/react-ts/package.json b/templates/react-ts/package.json
index bd51ae129ae..73c1f27c0c5 100644
--- a/templates/react-ts/package.json
+++ b/templates/react-ts/package.json
@@ -13,9 +13,9 @@
"spacetime:publish": "spacetime publish --project-path server --server maincloud"
},
"dependencies": {
- "spacetimedb": "workspace:*",
"react": "^18.3.1",
- "react-dom": "^18.3.1"
+ "react-dom": "^18.3.1",
+ "spacetimedb": "workspace:*"
},
"devDependencies": {
"@types/react": "^18.3.18",
diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json
index 438fe5b08c4..9dfb3aca4a6 100644
--- a/templates/remix-ts/.template.json
+++ b/templates/remix-ts/.template.json
@@ -3,10 +3,10 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "isbot",
+ "remix-run",
"react",
"react-dom",
- "remix-run",
+ "isbot",
"spacetimedb",
"types",
"typescript",
diff --git a/templates/remix-ts/package.json b/templates/remix-ts/package.json
index 155dd879d04..a7bae8c37cc 100644
--- a/templates/remix-ts/package.json
+++ b/templates/remix-ts/package.json
@@ -16,9 +16,9 @@
"@remix-run/node": "^2.16.0",
"@remix-run/react": "^2.16.0",
"@remix-run/serve": "^2.16.0",
- "isbot": "^5.1.17",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "isbot": "^5.1.17",
"spacetimedb": "workspace:*"
},
"devDependencies": {
diff --git a/templates/svelte-ts/.template.json b/templates/svelte-ts/.template.json
index aabee6e1c7b..d8281c96442 100644
--- a/templates/svelte-ts/.template.json
+++ b/templates/svelte-ts/.template.json
@@ -3,10 +3,10 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
- "spacetimedb",
"svelte",
- "svelte-check",
+ "spacetimedb",
"sveltejs",
+ "svelte-check",
"typescript",
"vite"
]
diff --git a/templates/svelte-ts/package.json b/templates/svelte-ts/package.json
index 98915fd9d95..4bded297466 100644
--- a/templates/svelte-ts/package.json
+++ b/templates/svelte-ts/package.json
@@ -13,11 +13,11 @@
"spacetime:publish": "spacetime publish --project-path spacetimedb --server maincloud"
},
"dependencies": {
+ "svelte": "^5.0.0",
"spacetimedb": "workspace:*"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.1.1",
- "svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "~5.6.2",
"vite": "^6.4.1"
diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json
index 581c38931fe..566adb72427 100644
--- a/templates/tanstack-ts/.template.json
+++ b/templates/tanstack-ts/.template.json
@@ -3,14 +3,14 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
+ "tanstack",
"react",
"react-dom",
"spacetimedb",
- "tanstack",
"types",
+ "vitejs",
"typescript",
"vite",
- "vite-tsconfig-paths",
- "vitejs"
+ "vite-tsconfig-paths"
]
}
diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json
index 3c5c05599c7..419f8a17214 100644
--- a/templates/vue-ts/.template.json
+++ b/templates/vue-ts/.template.json
@@ -3,11 +3,11 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
+ "vue",
"spacetimedb",
+ "vitejs",
"typescript",
"vite",
- "vitejs",
- "vue",
"vue-tsc"
]
}
diff --git a/templates/vue-ts/package.json b/templates/vue-ts/package.json
index c41aaac13b3..028fa395ab9 100644
--- a/templates/vue-ts/package.json
+++ b/templates/vue-ts/package.json
@@ -13,8 +13,8 @@
"spacetime:publish": "spacetime publish --project-path spacetimedb --server maincloud"
},
"dependencies": {
- "spacetimedb": "workspace:*",
- "vue": "^3.5.13"
+ "vue": "^3.5.13",
+ "spacetimedb": "workspace:*"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.4",
diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts
index ba93e63b93d..762ef4e685f 100644
--- a/tools/templates/update-template-jsons.ts
+++ b/tools/templates/update-template-jsons.ts
@@ -109,11 +109,16 @@ async function findManifests(dir: string): Promise<{ packageJson: string[]; carg
return { packageJson, cargoToml, csproj };
}
+/** Sort paths so root manifests come before subdirs (e.g. root package.json before spacetimedb/package.json) */
+function sortRootFirst(paths: string[]): string[] {
+ return [...paths].sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
+}
+
async function collectDepsFromManifests(templateDir: string): Promise {
const seen = new Set();
const { packageJson, cargoToml, csproj } = await findManifests(templateDir);
- for (const filePath of packageJson) {
+ for (const filePath of sortRootFirst(packageJson)) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parsePackageJson(content)) {
@@ -124,7 +129,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- for (const filePath of cargoToml) {
+ for (const filePath of sortRootFirst(cargoToml)) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parseCargoToml(content)) {
@@ -135,7 +140,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- for (const filePath of csproj) {
+ for (const filePath of sortRootFirst(csproj)) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parseCsproj(content)) {
@@ -146,7 +151,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- return [...seen].sort();
+ return [...seen];
}
export async function updateTemplateJsons(): Promise {
From 532ad01eb3b91f779e38a9fbd1e92e54e7f61c02 Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Thu, 5 Mar 2026 13:29:24 -0400
Subject: [PATCH 06/11] Update update-template-jsons.ts
---
tools/templates/update-template-jsons.ts | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts
index 762ef4e685f..ba93e63b93d 100644
--- a/tools/templates/update-template-jsons.ts
+++ b/tools/templates/update-template-jsons.ts
@@ -109,16 +109,11 @@ async function findManifests(dir: string): Promise<{ packageJson: string[]; carg
return { packageJson, cargoToml, csproj };
}
-/** Sort paths so root manifests come before subdirs (e.g. root package.json before spacetimedb/package.json) */
-function sortRootFirst(paths: string[]): string[] {
- return [...paths].sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
-}
-
async function collectDepsFromManifests(templateDir: string): Promise {
const seen = new Set();
const { packageJson, cargoToml, csproj } = await findManifests(templateDir);
- for (const filePath of sortRootFirst(packageJson)) {
+ for (const filePath of packageJson) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parsePackageJson(content)) {
@@ -129,7 +124,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- for (const filePath of sortRootFirst(cargoToml)) {
+ for (const filePath of cargoToml) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parseCargoToml(content)) {
@@ -140,7 +135,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- for (const filePath of sortRootFirst(csproj)) {
+ for (const filePath of csproj) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parseCsproj(content)) {
@@ -151,7 +146,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- return [...seen];
+ return [...seen].sort();
}
export async function updateTemplateJsons(): Promise {
From 334c2d8b8c25d5620938173c4ef831c48b317fae Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Thu, 5 Mar 2026 14:06:51 -0400
Subject: [PATCH 07/11] Update update-template-jsons.ts
---
tools/templates/update-template-jsons.ts | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts
index ba93e63b93d..762ef4e685f 100644
--- a/tools/templates/update-template-jsons.ts
+++ b/tools/templates/update-template-jsons.ts
@@ -109,11 +109,16 @@ async function findManifests(dir: string): Promise<{ packageJson: string[]; carg
return { packageJson, cargoToml, csproj };
}
+/** Sort paths so root manifests come before subdirs (e.g. root package.json before spacetimedb/package.json) */
+function sortRootFirst(paths: string[]): string[] {
+ return [...paths].sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
+}
+
async function collectDepsFromManifests(templateDir: string): Promise {
const seen = new Set();
const { packageJson, cargoToml, csproj } = await findManifests(templateDir);
- for (const filePath of packageJson) {
+ for (const filePath of sortRootFirst(packageJson)) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parsePackageJson(content)) {
@@ -124,7 +129,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- for (const filePath of cargoToml) {
+ for (const filePath of sortRootFirst(cargoToml)) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parseCargoToml(content)) {
@@ -135,7 +140,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- for (const filePath of csproj) {
+ for (const filePath of sortRootFirst(csproj)) {
try {
const content = await readFile(filePath, 'utf-8');
for (const dep of parseCsproj(content)) {
@@ -146,7 +151,7 @@ async function collectDepsFromManifests(templateDir: string): Promise
}
}
- return [...seen].sort();
+ return [...seen];
}
export async function updateTemplateJsons(): Promise {
From dcd816cbdbc437915288455d41937affae520fc6 Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Thu, 5 Mar 2026 14:11:48 -0400
Subject: [PATCH 08/11] templates
---
templates/basic-ts/.template.json | 1 -
templates/bun-ts/.template.json | 1 -
templates/chat-react-ts/.template.json | 1 -
templates/nextjs-ts/.template.json | 1 -
templates/nodejs-ts/.template.json | 2 +-
templates/react-ts/.template.json | 1 -
templates/remix-ts/.template.json | 1 -
templates/tanstack-ts/.template.json | 1 -
tools/templates/update-template-jsons.ts | 24 +++++++++++++++++++++---
9 files changed, 22 insertions(+), 11 deletions(-)
diff --git a/templates/basic-ts/.template.json b/templates/basic-ts/.template.json
index 4d1fbbdad84..e46d072d3ce 100644
--- a/templates/basic-ts/.template.json
+++ b/templates/basic-ts/.template.json
@@ -4,7 +4,6 @@
"server_lang": "typescript",
"builtWith": [
"spacetimedb",
- "types",
"esbuild",
"typescript"
]
diff --git a/templates/bun-ts/.template.json b/templates/bun-ts/.template.json
index 1d451e11df5..52bb8316796 100644
--- a/templates/bun-ts/.template.json
+++ b/templates/bun-ts/.template.json
@@ -4,7 +4,6 @@
"server_lang": "typescript",
"builtWith": [
"spacetimedb",
- "types",
"bun",
"typescript"
]
diff --git a/templates/chat-react-ts/.template.json b/templates/chat-react-ts/.template.json
index 4249f27c1bf..ce27697cc7f 100644
--- a/templates/chat-react-ts/.template.json
+++ b/templates/chat-react-ts/.template.json
@@ -8,7 +8,6 @@
"spacetimedb",
"eslint",
"testing-library",
- "types",
"vitejs",
"eslint-plugin-react-hooks",
"eslint-plugin-react-refresh",
diff --git a/templates/nextjs-ts/.template.json b/templates/nextjs-ts/.template.json
index c13284ad276..8f74dbc30e1 100644
--- a/templates/nextjs-ts/.template.json
+++ b/templates/nextjs-ts/.template.json
@@ -7,7 +7,6 @@
"react",
"react-dom",
"spacetimedb",
- "types",
"typescript"
]
}
diff --git a/templates/nodejs-ts/.template.json b/templates/nodejs-ts/.template.json
index 3ba16bd9954..c066ac463f3 100644
--- a/templates/nodejs-ts/.template.json
+++ b/templates/nodejs-ts/.template.json
@@ -3,8 +3,8 @@
"client_lang": "typescript",
"server_lang": "typescript",
"builtWith": [
+ "nodejs",
"spacetimedb",
- "types",
"esbuild",
"typescript",
"undici"
diff --git a/templates/react-ts/.template.json b/templates/react-ts/.template.json
index 21cae666fe3..588b144ed62 100644
--- a/templates/react-ts/.template.json
+++ b/templates/react-ts/.template.json
@@ -6,7 +6,6 @@
"react",
"react-dom",
"spacetimedb",
- "types",
"vitejs",
"typescript",
"vite"
diff --git a/templates/remix-ts/.template.json b/templates/remix-ts/.template.json
index 9dfb3aca4a6..916f18bbaff 100644
--- a/templates/remix-ts/.template.json
+++ b/templates/remix-ts/.template.json
@@ -8,7 +8,6 @@
"react-dom",
"isbot",
"spacetimedb",
- "types",
"typescript",
"vite"
]
diff --git a/templates/tanstack-ts/.template.json b/templates/tanstack-ts/.template.json
index 566adb72427..2d8a787a99c 100644
--- a/templates/tanstack-ts/.template.json
+++ b/templates/tanstack-ts/.template.json
@@ -7,7 +7,6 @@
"react",
"react-dom",
"spacetimedb",
- "types",
"vitejs",
"typescript",
"vite",
diff --git a/tools/templates/update-template-jsons.ts b/tools/templates/update-template-jsons.ts
index 762ef4e685f..cec556e4dc1 100644
--- a/tools/templates/update-template-jsons.ts
+++ b/tools/templates/update-template-jsons.ts
@@ -27,6 +27,11 @@ function normalizeNpmPackageName(name: string): string {
return name;
}
+/** Skip @types/* packages - typings, not frameworks */
+function shouldSkipPackage(normalized: string): boolean {
+ return normalized === 'types';
+}
+
function parsePackageJson(content: string): string[] {
const result: string[] = [];
let pkg: { dependencies?: Record; devDependencies?: Record };
@@ -38,7 +43,8 @@ function parsePackageJson(content: string): string[] {
for (const deps of [pkg.dependencies, pkg.devDependencies]) {
if (deps && typeof deps === 'object') {
for (const name of Object.keys(deps)) {
- result.push(normalizeNpmPackageName(name));
+ const normalized = normalizeNpmPackageName(name);
+ if (!shouldSkipPackage(normalized)) result.push(normalized);
}
}
}
@@ -114,13 +120,25 @@ function sortRootFirst(paths: string[]): string[] {
return [...paths].sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
}
-async function collectDepsFromManifests(templateDir: string): Promise {
+async function collectDepsFromManifests(templateDir: string, slug: string): Promise {
const seen = new Set();
const { packageJson, cargoToml, csproj } = await findManifests(templateDir);
+ const isNodeTemplate = slug.includes('nodejs');
for (const filePath of sortRootFirst(packageJson)) {
try {
const content = await readFile(filePath, 'utf-8');
+ const pkg = JSON.parse(content) as {
+ dependencies?: Record;
+ devDependencies?: Record;
+ };
+ if (
+ isNodeTemplate &&
+ ((pkg.dependencies && '@types/node' in pkg.dependencies) ||
+ (pkg.devDependencies && '@types/node' in pkg.devDependencies))
+ ) {
+ seen.add('nodejs');
+ }
for (const dep of parsePackageJson(content)) {
seen.add(dep);
}
@@ -186,7 +204,7 @@ export async function updateTemplateJsons(): Promise {
continue;
}
- const builtWith = await collectDepsFromManifests(templateDir);
+ const builtWith = await collectDepsFromManifests(templateDir, slug);
const { image: _image, ...rest } = meta;
const updatedMeta = { ...rest, builtWith };
From 96fca6a824fdadf894d3f67bee2d8d17da5150b6 Mon Sep 17 00:00:00 2001
From: bradleyshep <148254416+bradleyshep@users.noreply.github.com>
Date: Thu, 5 Mar 2026 18:49:36 -0400
Subject: [PATCH 09/11] dynamic readme generation
---
tools/templates/README.md | 2 +-
tools/templates/generate-template-readmes.ts | 102 +++++++++++++++----
2 files changed, 81 insertions(+), 23 deletions(-)
diff --git a/tools/templates/README.md b/tools/templates/README.md
index 358ae5aebea..151bb130dcc 100644
--- a/tools/templates/README.md
+++ b/tools/templates/README.md
@@ -4,7 +4,7 @@ Scripts for maintaining template READMEs and metadata in the SpacetimeDB repo. O
## Scripts
-- **generate-readmes** – Converts quickstart MDX docs to Markdown and writes `templates//README.md`
+- **generate-readmes** – Converts quickstart MDX docs to Markdown and writes `templates//README.md`. Discovers mappings by parsing `--template X` from quickstart files. Templates can override with a `quickstart` field in `.template.json` (must point to a file in the quickstarts dir).
- **update-jsons** – Updates `builtWith` in each `templates//.template.json` from package.json, Cargo.toml, and .csproj manifests
- **generate** – Runs both (readmes first, then jsons)
diff --git a/tools/templates/generate-template-readmes.ts b/tools/templates/generate-template-readmes.ts
index 608e0aa5b92..2740d4ad001 100644
--- a/tools/templates/generate-template-readmes.ts
+++ b/tools/templates/generate-template-readmes.ts
@@ -8,7 +8,7 @@
* Usage: pnpm run generate-readmes (from tools/templates/)
*/
-import { readFile, writeFile } from 'node:fs/promises';
+import { readFile, readdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -18,24 +18,40 @@ const TEMPLATES_DIR = path.join(REPO_ROOT, 'templates');
const QUICKSTARTS_DIR = path.join(REPO_ROOT, 'docs/docs/00100-intro/00200-quickstarts');
const DOCS_ROOT = path.join(REPO_ROOT, 'docs/docs');
-const TEMPLATE_TO_QUICKSTART: Record = {
- 'react-ts': '00100-react.md',
- 'nextjs-ts': '00150-nextjs.md',
- 'vue-ts': '00150-vue.md',
- 'nuxt-ts': '00155-nuxt.md',
- 'svelte-ts': '00160-svelte.md',
- 'angular-ts': '00165-angular.md',
- 'tanstack-ts': '00170-tanstack.md',
- 'remix-ts': '00175-remix.md',
- 'browser-ts': '00180-browser.md',
- 'bun-ts': '00250-bun.md',
- 'deno-ts': '00275-deno.md',
- 'nodejs-ts': '00300-nodejs.md',
- 'basic-ts': '00400-typescript.md',
- 'basic-rs': '00500-rust.md',
- 'basic-cs': '00600-c-sharp.md',
- 'basic-cpp': '00700-cpp.md',
-};
+const TEMPLATE_FROM_QUICKSTART_RE = /--template\s+(\S+)/;
+
+/** Parse --template X from quickstart content. Returns template slug or null. */
+function parseTemplateFromQuickstart(content: string): string | null {
+ const match = content.match(TEMPLATE_FROM_QUICKSTART_RE);
+ return match ? match[1] : null;
+}
+
+/** Discover template -> quickstart mapping by parsing --template from each quickstart file. */
+async function discoverQuickstartMapping(): Promise