# Jazz
## Documentation
### Getting started
#### Introduction
# Learn some Jazz
Welcome to the Jazz documentation!
The Jazz docs are currently heavily work in progress, sorry about that!
## Quickstart
You can use [`create-jazz-app`](/docs/tools/create-jazz-app) to create a new Jazz project from one of our starter templates or example apps:
```sh
npx create-jazz-app@latest --api-key you@example.com
```
Or set up Jazz yourself, using the following instructions for your framework of choice:
- [React](/docs/react/project-setup)
- [Next.js](/docs/react/project-setup#nextjs)
- [React Native](/docs/react-native/project-setup)
- [React Native Expo](/docs/react-native-expo/project-setup)
- [Vue](/docs/vue/project-setup)
- [Svelte](/docs/svelte/project-setup)
Or you can follow this [React step-by-step guide](/docs/react/guide) where we walk you through building an issue tracker app.
## Example apps
You can also find [example apps](/examples) with code most similar to what you want to build. These apps
make use of different features such as auth, file upload, and more.
## Sync and storage
Sync and persist your data by setting up a [sync and storage infrastructure](/docs/sync-and-storage) using Jazz Cloud, or do it yourself.
## Collaborative values
Learn how to structure your data using [collaborative values](/docs/schemas/covalues).
## API Reference
Many of the packages provided are documented in the [API Reference](/api-reference).
## LLM Docs
Get better results with AI by [importing the Jazz docs](/docs/ai-tools) into your context window.
## Get support
If you have any questions or need assistance, please don't hesitate to reach out to us on [Discord](https://discord.gg/utDMjHYg42).
We would love to help you get started.
#### Guide
### react Implementation
# React guide
This is a step-by-step tutorial where we'll build an issue tracker app using React.
You'll learn how to set up a Jazz app, use Jazz Cloud for sync and storage, create and manipulate data using
Collaborative Values (CoValues), build a UI and subscribe to changes, set permissions, and send invites.
## Project setup
1. Create a project called "circular" from a generic Vite starter template:
{/* prettier-ignore */}
```bash
npx degit gardencmp/vite-ts-react-tailwind circular
cd circular
npm install
npm run dev
```
You should now have an empty app running, typically at [localhost:5173](http://localhost:5173).
(If you make changes to the code, the app will automatically refresh.)
2. Install `jazz-tools` and `jazz-react`
(in a new terminal window):
{/* prettier-ignore */}
```bash
cd circular
npm install jazz-tools jazz-react
```
3. Modify `src/main.tsx` to set up a Jazz context:
{/* prettier-ignore */}
```tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { JazzProvider } from "jazz-react"; // [!code ++]
ReactDOM.createRoot(document.getElementById("root")!).render(
);
```
This sets Jazz up and wraps our app in the provider.
{/* TODO: explain Auth */}
## Intro to CoValues
Let's learn about the **central idea** behind Jazz: **Collaborative Values.**
What if we could **treat distributed state like local state?** That's what CoValues do.
We can
- **create** CoValues, anywhere
- **load** CoValues by `ID`, from anywhere else
- **edit** CoValues, from anywhere, by mutating them like local state
- **subscribe to edits** in CoValues, whether they're local or remote
### Declaring our own CoValues
To make our own CoValues, we first need to declare a schema for them. Think of a schema as a combination of TypeScript types and runtime type information.
Let's start by defining a schema for our most central entity in Circular: an **Issue.**
Create a new file `src/schema.ts` and add the following:
```ts
export class Issue extends CoMap {
title = co.string;
description = co.string;
estimate = co.number;
status = co.optional.literal("backlog", "in progress", "done");
}
```
{/* TODO: explain what's happening */}
### Reading from CoValues
CoValues are designed to be read like simple local JSON state. Let's see how we can read from an Issue by building a component to render one.
Create a new file `src/components/Issue.tsx` and add the following:
{/* prettier-ignore */}
```tsx
export function IssueComponent({ issue }: { issue: Issue }) {
return (
{issue.title}
{issue.description}
Estimate: {issue.estimate}
Status: {issue.status}
);
}
```
Simple enough!
### Creating CoValues
To actually see an Issue, we have to create one. This is where things start to get interesting...
Let's modify `src/App.tsx` to prepare for creating an Issue and then rendering it:
{/* prettier-ignore */}
```tsx
function App() {
const [issue, setIssue] = useState();
if (issue) {
return ;
} else {
return ;
}
}
export default App;
```
Now, finally, let's implement creating an issue:
{/* prettier-ignore */}
```tsx
function App() {
const [issue, setIssue] = useState();
const createIssue = () => { // [!code ++:11]
const newIssue = Issue.create(
{
title: "Buy terrarium",
description: "Make sure it's big enough for 10 snails.",
estimate: 5,
status: "backlog",
},
);
setIssue(newIssue);
};
if (issue) {
return ;
} else {
return ;
}
}
export default App;
```
π Now you should be able to create a new issue by clicking the button and then see it rendered!
Preview
Buy terrarium
Make sure it's big enough for 10 snails.
Estimate: 5
Status: backlog
We'll already notice one interesting thing here:
- We have to create every CoValue with an `owner`!
- this will determine access rights on the CoValue, which we'll learn about in "Groups & Permissions"
- here the `owner` is set automatically to a group managed by the current user because we have not declared any
**Behind the scenes, Jazz not only creates the Issue in memory but also automatically syncs an encrypted version to the cloud and persists it locally. The Issue also has a globally unique ID.**
We'll make use of both of these facts in a bit, but for now let's start with local editing and subscribing.
### Editing CoValues and subscribing to edits
Since we're the owner of the CoValue, we should be able to edit it, right?
And since this is a React app, it would be nice to subscribe to edits of the CoValue and reactively re-render the UI, like we can with local state.
This is exactly what the `useCoState` hook is for!
- Note that `useCoState` doesn't take a CoValue directly, but rather a CoValue's schema, plus its `ID`.
- So we'll slightly adapt our `useState` to only keep track of an issue ID...
- ...and then use `useCoState` to get the actual issue
Let's modify `src/App.tsx`:
{/* prettier-ignore */}
```tsx
function App() {
const [issue, setIssue] = useState(); // [!code --]
const [issueID, setIssueID] = useState>(); // [!code ++]
const issue = useCoState(Issue, issueID); // [!code ++]
const createIssue = () => {
const newIssue = Issue.create(
{
title: "Buy terrarium",
description: "Make sure it's big enough for 10 snails.",
estimate: 5,
status: "backlog",
},
);
setIssueID(newIssue.id);
};
if (issue) {
return ;
} else {
return ;
}
}
export default App;
```
And now for the exciting part! Let's make `src/components/Issue.tsx` an editing component.
{/* prettier-ignore */}
```tsx
export function IssueComponent({ issue }: { issue: Issue }) {
return (
{ issue.title = event.target.value }}/>
);
}
```
Preview
π Now you should be able to edit the issue after creating it!
You'll immediately notice that we're doing something non-idiomatic for React: we mutate the issue directly, by assigning to its properties.
This works because CoValues
- intercept these edits
- update their local view accordingly (React doesn't really care after rendering)
- notify subscribers of the change (who will receive a fresh, updated view of the CoValue)
We have one subscriber on our Issue, with `useCoState` in `src/App.tsx`, which will cause the `App` component and its children **to** re-render whenever the Issue changes.
### Automatic local & cloud persistence
So far our Issue CoValues just looked like ephemeral local state. We'll now start exploring the first main feature that makes CoValues special: **automatic persistence.**
Actually, all the Issue CoValues we've created so far **have already been automatically persisted** to the cloud and locally - but we lose track of their ID after a reload.
So let's store the ID in the browser's URL and make sure our useState is in sync with that.
{/* prettier-ignore */}
```tsx
function App() {
const [issueID, setIssueID] = useState | undefined>( // [!code ++:3]
(window.location.search?.replace("?issue=", "") || undefined) as ID | undefined,
);
const issue = useCoState(Issue, issueID);
const createIssue = () => {
const newIssue = Issue.create(
{
title: "Buy terrarium",
description: "Make sure it's big enough for 10 snails.",
estimate: 5,
status: "backlog",
},
);
setIssueID(newIssue.id);
window.history.pushState({}, "", `?issue=${newIssue.id}`); // [!code ++]
};
if (issue) {
return ;
} else {
return ;
}
}
export default App;
```
π Now you should be able to create an issue, edit it, reload the page, and still see the same issue.
### Remote sync
To see that sync is also already working, try the following:
- copy the URL to a new tab in the same browser window and see the same issue
- edit the issue and see the changes reflected in the other tab!
This works because we load the issue as the same account that created it and owns it (remember how you set `{ owner: me }`).
But how can we share an Issue with someone else?
### Simple public sharing
We'll learn more about access control in "Groups & Permissions", but for now let's build a super simple way of sharing an Issue by just making it publicly readable & writable.
All we have to do is create a new group to own each new issue and add "everyone" as a "writer":
{/* prettier-ignore */}
```tsx
function App() {
const { me } = useAccount(); // [!code ++]
const [issueID, setIssueID] = useState | undefined>(
(window.location.search?.replace("?issue=", "") || undefined) as ID | undefined,
);
const issue = useCoState(Issue, issueID);
const createIssue = () => {
const group = Group.create(); // [!code ++:2]
group.addMember("everyone", "writer");
const newIssue = Issue.create(
{
title: "Buy terrarium",
description: "Make sure it's big enough for 10 snails.",
estimate: 5,
status: "backlog",
},
{ owner: group }, // [!code ++]
);
setIssueID(newIssue.id);
window.history.pushState({}, "", `?issue=${newIssue.id}`);
};
if (issue) {
return ;
} else {
return ;
}
}
export default App;
```
π Now you should be able to open the Issue (with its unique URL) on another device or browser, or send it to a friend and you should be able to **edit it together in realtime!**
This concludes our intro to the essence of CoValues. Hopefully you're starting to have a feeling for how CoValues behave and how they're magically available everywhere.
## Refs & auto-subscribe
Now let's have a look at how to compose CoValues into more complex structures and build a whole app around them.
Let's extend our two data model to include "Projects" which have a list of tasks and some properties of their own.
Using plain objects, you would probably type a Project like this:
```ts
type Project = {
name: string;
issues: Issue[];
};
```
In order to create this more complex structure in a fully collaborative way, we're going to need _references_ that allow us to nest or link CoValues.
Add the following to `src/schema.ts`:
```ts
export class Issue extends CoMap {
title = co.string;
description = co.string;
estimate = co.number;
status? = co.optional.literal("backlog", "in progress", "done");
}
export class ListOfIssues extends CoList.Of(co.ref(Issue)) {} // [!code ++:6]
export class Project extends CoMap {
name = co.string;
issues = co.ref(ListOfIssues);
}
```
Now let's change things up a bit in terms of components as well.
First, we'll change `App.tsx` to create and render `Project`s instead of `Issue`s. (We'll move the `useCoState` into the `ProjectComponent` we'll create in a second).
{/* prettier-ignore */}
```tsx
function App() {
const [projectID, setProjectID] = useState | undefined>( // [!code ++:3]
(window.location.search?.replace("?project=", "") || undefined) as ID | undefined
);
const issue = useCoState(Issue, issueID); // [!code --]
const createProject = () => { // [!code ++:14]
const group = Group.create();
group.addMember("everyone", "writer");
const newProject = Project.create(
{
name: "New Project",
issues: ListOfIssues.create([], { owner: group })
},
group,
);
setProjectID(newProject.id);
window.history.pushState({}, "", `?project=${newProject.id}`);
};
if (projectID) { // [!code ++:4]
return ;
} else {
return ;
}
}
export default App;
```
Now we'll actually create the `ProjectComponent` that renders a `Project` and its `Issue`s.
Create a new file `src/components/Project.tsx` and add the following:
{/* prettier-ignore */}
```tsx
export function ProjectComponent({ projectID }: { projectID: ID }) {
const project = useCoState(Project, projectID);
const createAndAddIssue = () => {
project?.issues?.push(Issue.create({
title: "",
description: "",
estimate: 0,
status: "backlog",
}, project._owner));
};
return project ? (
{project.name}
{project.issues?.map((issue) => (
issue &&
))}
) : project === null ? (
Project not found or access denied
) : (
Loading project...
);
}
```
π Now you should be able to create a project, add issues to it, share it, and edit it collaboratively!
Two things to note here:
- We create a new Issue like before, and then push it into the `issues` list of the Project. By setting the `owner` to the Project's owner, we ensure that the Issue has the same access rights as the project itself.
- We only need to use `useCoState` on the Project, and the nested `ListOfIssues` and each `Issue` will be **automatically loaded and subscribed to when we access them.**
- However, because either the `Project`, `ListOfIssues`, or each `Issue` might not be loaded yet, we have to check for them being defined.
### Precise resolve queries
The load-and-subscribe-on-access is a convenient way to have your rendering drive data loading (including in nested components!) and lets you quickly chuck UIs together without worrying too much about the shape of all data you'll need.
But you can also take more precise control over loading by defining a minimum-depth to load in `useCoState`:
{/* prettier-ignore */}
```tsx
export function ProjectComponent({ projectID }: { projectID: ID }) {
const project = useCoState(
Project,
projectID,
{ resolve: { issues: { $each: true } } } // [!code ++]
);
const createAndAddIssue = () => {
project?.issues.push(Issue.create({
title: "",
description: "",
estimate: 0,
status: "backlog",
}, project._owner));
};
return project ? (
{project.name}
{project.issues.map((issue) => (
))}
) : (
Loading project...
);
}
```
The resolve query `{ resolve: { issues: { $each: true } } }` means "in `Project`, load `issues` and load each item in `issues` deeply". (Since an `Issue` doesn't have any further references, "deeply" actually means all its properties will be available).
- Now, we can get rid of a lot of conditional accesses because we know that once `project` is loaded, `project.issues` and each `Issue` in it will be loaded as well.
- This also results in only one rerender and visual update when everything is loaded, which is faster (especially for long lists) and gives you more control over the loading UX.
{/* TODO: explain about not loaded vs not set/defined and `_refs` basics */}
## Groups & permissions
We've seen briefly how we can use Groups to give everyone access to a Project,
and how we can use `{ owner: me }` to make something private to the current user.
### Groups / Accounts as permission scopes
This gives us a hint of how permissions work in Jazz: **every CoValue has an owner,
and the access rights on that CoValue are determined by its owner.**
- If the owner is an Account, only that Account can read and write the CoValue.
- If the owner is a Group, the access rights depend on the *role* of the Account (that is trying to access the CoValue) in that Group.
- `"reader"`s can read but not write to CoValues belonging to the Group.
- `"writer"`s can read and write to CoValues belonging to the Group.
- `"admin"`s can read and write to CoValues belonging to the Group *and can add and remove other members from the Group itself.*
### Creating invites
There is also an abstraction for creating *invitations to join a Group* (with a specific role) that you can use
to add people without having to know their Account ID.
Let's use these abstractions to build teams for a Project that we can invite people to.
Turns out, we're already mostly there! First, let's remove making the Project public:
{/* prettier-ignore */}
```tsx
function App() {
const [projectID, setProjectID] = useState | undefined>(
(window.location.search?.replace("?project=", "") || undefined) as ID | undefined,
);
const createProject = () => {
const group = Group.create();
group.addMember("everyone", "writer"); // [!code --]
const newProject = Project.create(
{
name: "New Project",
issues: ListOfIssues.create([], { owner: group })
},
group,
);
setProjectID(newProject.id);
window.history.pushState({}, "", `?project=${newProject.id}`);
};
if (projectID) {
return ;
} else {
return ;
}
}
export default App;
```
Now, inside ProjectComponent, let's add a button to invite guests (read-only) or members (read-write) to the Project.
{/* prettier-ignore */}
```tsx
export function ProjectComponent({ projectID }: { projectID: ID }) {
const project = useCoState(Project, projectID, { resolve: { issues: { $each: true } } });
const { me } = useAccount(); // [!code ++:6]
const invite = (role: "reader" | "writer") => {
const link = createInviteLink(project, role, { valueHint: "project" });
navigator.clipboard.writeText(link);
};
const createAndAddIssue = () => {
project?.issues.push(Issue.create({
title: "",
description: "",
estimate: 0,
status: "backlog",
}, project._owner));
};
return project ? (
);
}
```
### Consuming invites
#### Example apps
#### FAQs
# Frequently Asked Questions
## How established is Jazz?
Jazz is backed by fantastic angel and institutional investors with experience and know-how in devtools and has been in development since 2020.
## Will Jazz be around long-term?
We're committed to Jazz being around for a long time! We understand that when you choose Jazz for your projects, you're investing time and making a significant architectural choice, and we take that responsibility seriously.
That's why we've designed Jazz with longevity in mind from the start:
- The open source nature of our sync server means you'll always be able to run your own infrastructure
- Your data remains accessible even if our cloud services change
- We're designing the protocol as an open specification
This approach creates a foundation that can continue regardless of any single company's involvement. The local-first architecture means your apps will always work, even offline, and your data remains yours.
### Project setup
#### Installation
### react-native-expo Implementation
# React Native (Expo) Installation and Setup
Jazz supports Expo through the dedicated `jazz-expo` package, which is specifically designed for Expo applications. If you're building for React Native without Expo, please refer to the [React Native](/docs/react-native/project-setup) guide instead.
Jazz requires an [Expo development build](https://docs.expo.dev/develop/development-builds/introduction/) using [Expo Prebuild](https://docs.expo.dev/workflow/prebuild/) for native code. It is **not compatible** with Expo Go. Jazz also supports the [New Architecture](https://docs.expo.dev/guides/new-architecture/).
Tested with:
```json
"expo": "~52.0.0",
"react-native": "0.76.7",
"react": "18.3.1"
```
## Installation
### Create a new project
(Skip this step if you already have one)
```bash
npx create-expo-app -e with-router-tailwind my-jazz-app
cd my-jazz-app
npx expo prebuild
```
### Install dependencies
```bash
# Expo dependencies
npx expo install expo-linking expo-secure-store expo-file-system @react-native-community/netinfo @bam.tech/react-native-image-resizer
# React Native polyfills
npm i -S @azure/core-asynciterator-polyfill react-native-url-polyfill readable-stream react-native-get-random-values @craftzdog/react-native-buffer
# Jazz dependencies
npm i -S jazz-tools jazz-expo jazz-react-native-media-images
```
**Note**: Hermes has added support for `atob` and `btoa` in React Native 0.74. If you are using earlier versions, you may also need to polyfill `atob` and `btoa` in your `package.json`. Packages to try include: `text-encoding`, `base-64`, and you can drop `@bacons/text-decoder`.
#### Fix incompatible dependencies
If you encounter incompatible dependencies, you can try to fix them with the following command:
```bash
npx expo install --fix
```
### Configure Metro
#### Regular repositories
If you are not working within a monorepo, create a new file `metro.config.js` in the root of your project with the following content:
```ts twoslash
// @noErrors: 2304
// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(projectRoot);
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
module.exports = config;
```
#### Monorepos
For monorepos, use the following `metro.config.js`:
```ts twoslash
// @noErrors: 2304
// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { FileStore } = require("metro-cache");
const path = require("path");
// eslint-disable-next-line no-undef
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
const config = getDefaultConfig(projectRoot);
config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
config.cacheStores = [
new FileStore({
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
}),
];
module.exports = config;
```
### Additional monorepo configuration (for pnpm)
If you're using `pnpm`, you'll need to make sure that your expo app's `package.json` has this:
```json
// package.json
{
"main": "index.js",
...
}
```
For more information, refer to [this Expo monorepo example](https://github.com/byCedric/expo-monorepo-example#pnpm-workarounds).
## Authentication
Jazz provides authentication to help users access their data across multiple devices. For details on implementing authentication with Expo, check our [Authentication Overview](/docs/authentication/overview) guide and see the [Expo Chat Demo](https://github.com/garden-co/jazz/tree/main/examples/chat-rn-expo-clerk) for a complete example.
## Next Steps
Now that you've set up your Expo project for Jazz, you'll need to:
1. [Set up the Jazz Provider](/docs/react-native-expo/project-setup/providers) - Configure how your app connects to Jazz
2. [Add authentication](/docs/authentication/overview) (optional) - Enable users to access data across devices
3. Define your schema - See the [schema docs](/docs/schemas/covalues) for more information
4. Run your app:
```sh
npx expo run:ios
# or
npx expo run:android
```
## Verification
Ready to see if everything's working? Let's fire up your app:
```sh
npx expo run:ios
# or
npx expo run:android
```
If all goes well, your app should start up without any angry red error screens. Take a quick look at the Metro console too - no Jazz-related errors there means you're all set! If you see your app's UI come up smoothly, you've nailed the installation.
If you run into any issues that aren't covered in the Common Issues section, [drop by our Discord for help](https://discord.gg/utDMjHYg42).
## Common Issues
- **Metro bundler errors**: If you see errors about missing polyfills, ensure all polyfills are properly imported.
- **iOS build failures**: Make sure you've run `pod install` after adding the dependencies.
- **Android build failures**: Ensure you've run `npx expo prebuild` to generate native code.
- **Expo Go incompatibility**: Remember that Jazz requires a development build and won't work with Expo Go.
### Install CocoaPods
If you're compiling for iOS, you'll need to install CocoaPods for your project. If you need to install it, we recommend using [`pod-install`](https://www.npmjs.com/package/pod-install):
```bash
npx pod-install
```
---
### react-native Implementation
# React Native Installation and Setup
This guide covers setting up Jazz for React Native applications from scratch. If you're using Expo, please refer to the [React Native - Expo](/docs/react-native-expo/project-setup) guide instead. If you just want to get started quickly, you can use our [React Native Chat Demo](https://github.com/garden-co/jazz/tree/main/examples/chat-rn) as a starting point.
Jazz supports the [New Architecture](https://reactnative.dev/architecture/landing-page) for React Native.
Tested with:
```json
"react-native": "0.78.22",
"react": "18.3.1"
```
## Installation
### Create a new project
(Skip this step if you already have one)
```bash
npx @react-native-community/cli init myjazzapp
cd myjazzapp
```
If you intend to build for iOS, you can accept the invitation to install CocoaPods. If you decline, or you get an error, [you can install it with `pod-install`](#install-cocoapods).
### Install dependencies
```bash
# React Native dependencies
npm install @react-native-community/netinfo @bam.tech/react-native-image-resizer
# React Native polyfills
npm i -S @azure/core-asynciterator-polyfill react-native-url-polyfill readable-stream react-native-get-random-values @craftzdog/react-native-buffer @op-engineering/op-sqlite react-native-mmkv
# Jazz dependencies
npm i -S jazz-tools jazz-react-native jazz-react-native-media-images
```
**Note**: Hermes has added support for `atob` and `btoa` in React Native 0.74. If you are using earlier versions, you may also need to polyfill `atob` and `btoa` in your `package.json`. Packages to try include `text-encoding` and `base-64`, and you can drop `@bacons/text-decoder`.
### Configure Metro
#### Regular repositories
If you are not working within a monorepo, create a new file `metro.config.js` in the root of your project with the following content:
```ts twoslash
// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const config = {
resolver: {
sourceExts: ["mjs", "js", "json", "ts", "tsx"],
requireCycleIgnorePatterns: [/(^|\/|\\)node_modules($|\/|\\)/]
}
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
```
#### Monorepos
For monorepos, use the following `metro.config.js`:
```ts twoslash
// metro.config.js
const path = require("path");
const { makeMetroConfig } = require("@rnx-kit/metro-config");
const MetroSymlinksResolver = require("@rnx-kit/metro-resolver-symlinks");
// Define workspace root
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
// Add packages paths
const extraNodeModules = {
modules: path.resolve(workspaceRoot, "node_modules"),
};
const watchFolders = [
path.resolve(workspaceRoot, "node_modules"),
path.resolve(workspaceRoot, "packages"),
];
const nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
module.exports = makeMetroConfig({
resolver: {
resolveRequest: MetroSymlinksResolver(),
extraNodeModules,
nodeModulesPaths,
},
sourceExts: ["mjs", "js", "json", "ts", "tsx"],
watchFolders,
});
```
### Additional monorepo configuration (for pnpm)
- Add `node-linker=hoisted` to the root `.npmrc` (create this file if it doesn't exist).
- Add the following to the root `package.json`:
```json
// package.json
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@babel/*",
"typescript"
]
}
}
```
### Add polyfills
Create a file `polyfills.js` at the project root with the following content:
```ts twoslash
// @noErrors: 7016
// polyfills.js
polyfillGlobal("Buffer", () => Buffer); // polyfill Buffer
polyfillGlobal("ReadableStream", () => ReadableStream); // polyfill ReadableStream
```
Update `index.js`:
```ts twoslash
// @noErrors: 2307
// index.js
AppRegistry.registerComponent(appName, () => App);
```
Lastly, ensure that the `"main"` field in your `package.json` points to `index.js`:
```json
// package.json
{
"main": "index.js",
...
}
```
## Authentication
Jazz provides authentication to help users access their data across multiple devices. For details on implementing authentication, check our [Authentication Overview](/docs/authentication/overview) guide and see the [React Native Chat Demo](https://github.com/garden-co/jazz/tree/main/examples/chat-rn) for a complete example.
## Next Steps
Now that you've set up your React Native project for Jazz, you'll need to:
1. [Set up the Jazz Provider](/docs/react-native/project-setup/providers) - Configure how your app connects to Jazz
2. [Add authentication](/docs/authentication/overview) (optional) - Enable users to access data across devices
3. Define your schema - See the [schema docs](/docs/schemas/covalues) for more information
4. Run your app:
```sh
npx react-native run-ios
npx react-native run-android
```
## Verification
Ready to see if everything's working? Let's fire up your app:
```sh
npx react-native run-ios
# or
npx react-native run-android
```
If all goes well, your app should start up without any angry red error screens. Take a quick look at the Metro console too - no Jazz-related errors there means you're all set! If you see your app's UI come up smoothly, you've nailed the installation.
If you run into any issues that aren't covered in the Common Issues section, [drop by our Discord for help](https://discord.gg/utDMjHYg42).
## Common Issues
- **Metro bundler errors**: If you see errors about missing polyfills, ensure all polyfills are properly imported in your `polyfills.js` file.
- **iOS build failures**: Make sure you've run `pod install` after adding the dependencies.
- **Android build failures**: Ensure your Android SDK and NDK versions are compatible with the native modules.
### Install CocoaPods
If you're compiling for iOS, you'll need to install CocoaPods for your project. If you need to install it, we recommend using [`pod-install`](https://www.npmjs.com/package/pod-install):
```bash
npx pod-install
```
---
### react Implementation
# React
Wrap your application with ``, this is where you specify the sync & storage server to connect to (see [Sync and storage](/docs/react/sync-and-storage)).
{/* prettier-ignore */}
```tsx
ReactDOM.createRoot(document.getElementById("root")!).render(
);
// [!code ++:6]
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
declare module "jazz-react" {
interface Register {
Account: MyAppAccount;
}
}
```
## Next.js
### Client-side only
The easiest way to use Jazz with Next.JS is to only use it on the client side. You can ensure this by:
- marking the Jazz provider file as `"use client"`
{/* prettier-ignore */}
```tsx
"use client" // [!code ++]
export function MyJazzProvider(props: { children: React.ReactNode }) {
return (
{props.children}
);
}
```
- marking any file with components where you use Jazz hooks (such as `useAccount` or `useCoState`) as `"use client"`
### SSR use (experimental)
Pure SSR use of Jazz is basically just using jazz-nodejs (see [Node.JS / Server Workers](/docs/react/project-setup/server-side)) inside Server Components.
Instead of using hooks as you would on the client, you await promises returned by `CoValue.load(...)` inside your Server Components.
TODO: code example
This should work well for cases like rendering publicly-readable information, since the worker account will be able to load them.
In the future, it will be possible to use trusted auth methods (such as Clerk, Auth0, etc.) that let you act as the same Jazz user both on the client and on the server, letting you use SSR even for data private to that user.
### SSR + client-side (experimental)
You can combine the two approaches by creating
1. A pure "rendering" component that renders an already-loaded CoValue (in JSON-ified form)
TODO: code example
2. A "hydrating" component (with `"use client"`) that
- expects a pre-loaded CoValue as a prop (in JSON-ified form)
- uses one of the client-side Jazz hooks (such as `useAccount` or `useCoState`) to subscribe to that same CoValue
- passing the client-side subscribed state to the "rendering" component, with the pre-loaded CoValue as a fallback until the client receives the first subscribed state
TODO: code example
3. A "pre-loading" Server Component that
- pre-loads the CoValue by awaiting it's `load(...)` method (as described above)
- renders the "hydrating" component, passing the pre-loaded CoValue as a prop
TODO: code example
---
### server-side Implementation
# Node.JS / server workers
The main detail to understand when using Jazz server-side is that Server Workers have Jazz `Accounts`, just like normal users do.
This lets you share CoValues with Server Workers, having precise access control by adding the Worker to `Groups` with specific roles just like you would with other users.
## Generating credentials
Server Workers typically have static credentials, consisting of a public Account ID and a private Account Secret.
To generate new credentials for a Server Worker, you can run:
{/* prettier-ignore */}
```sh
npx jazz-run account create --name "My Server Worker"
```
The name will be put in the public profile of the Server Worker's `Account`, which can be helpful when inspecting metadata of CoValue edits that the Server Worker has done.
## Storing & providing credentials
Server Worker credentials are typically stored and provided as environmental variables.
**Take extra care with the Account Secret — handle it like any other secret environment variable such as a DB password.**
## Starting a server worker
You can use `startWorker` from `jazz-nodejs` to start a Server Worker. Similarly to setting up a client-side Jazz context, it:
- takes a custom `AccountSchema` if you have one (for example, because the worker needs to store information in it's private account root)
- takes a URL for a sync & storage server
`startWorker` expects credentials in the `JAZZ_WORKER_ACCOUNT` and `JAZZ_WORKER_SECRET` environment variables by default (as printed by `npx account create ...`), but you can also pass them manually as `accountID` and `accountSecret` parameters if you get them from elsewhere.
{/* prettier-ignore */}
```ts
const { worker } = await startWorker({
AccountSchema: MyWorkerAccount,
syncServer: 'wss://cloud.jazz.tools/?key=you@example.com',
});
```
`worker` acts like `me` (as returned by `useAccount` on the client) - you can use it to:
- load/subscribe to CoValues: `MyCoValue.subscribe(id, worker, {...})`
- create CoValues & Groups `const val = MyCoValue.create({...}, { owner: worker })`
## Using CoValues instead of requests
Just like traditional backend functions, you can use Server Workers to do useful stuff (computations, calls to third-party APIs etc.) and put the results back into CoValues, which subscribed clients automatically get notified about.
What's less clear is how you can trigger this work to happen.
- One option is to define traditional HTTP API handlers that use the Jazz Worker internally. This is helpful if you need to mutate Jazz state in response to HTTP requests such as for webhooks or non-Jazz API clients
- The other option is to have the Jazz Worker subscribe to CoValues which they will then collaborate on with clients.
- A common pattern is to implement a state machine represented by a CoValue, where the client will do some state transitions (such as `draft -> ready`), which the worker will notice and then do some work in response, feeding the result back in a further state transition (such as `ready -> success & data`, or `ready -> failure & error details`).
- This way, client and worker don't have to explicitly know about each other or communicate directly, but can rely on Jazz as a communication mechanism - with computation progressing in a distributed manner wherever and whenever possible.
---
### svelte Implementation
# Svelte Installation
Jazz can be used with Svelte or in a SvelteKit app.
To add some Jazz to your Svelte app, you can use the following steps:
1. Install Jazz dependencies
```sh
pnpm install jazz-tools jazz-svelte
```
2. Write your schema
See the [schema docs](/docs/schemas/covalues) for more information.
```ts
// src/lib/schema.ts
export class MyProfile extends Profile {
name = co.string;
counter = co.number; // This will be publically visible
}
export class MyAccount extends Account {
profile = co.ref(MyProfile);
// ...
}
```
3. Set up the Provider in your root layout
```svelte
```
4. Use Jazz hooks in your components
```svelte
```
For a complete example of Jazz with Svelte, check out our [file sharing example](https://github.com/gardencmp/jazz/tree/main/examples/file-share-svelte) which demonstrates, Passkey authentication, file uploads and access control.
---
### vue Implementation
# VueJS demo todo app guide
This guide provides step-by-step instructions for setting up and running a Jazz-powered Todo application using VueJS.
See the full example [here](https://github.com/garden-co/jazz/tree/main/examples/todo-vue).
---
## Setup
### Create a new app
Run the following command to create a new VueJS application:
```bash
β― pnpm create vue@latest
β Project name: β¦ vue-setup-guide
β Add TypeScript? β¦ Yes
β Add JSX Support? β¦ No
β Add Vue Router for Single Page Application development? β¦ Yes
β Add Pinia for state management? β¦ No
β Add Vitest for Unit Testing? β¦ No
β Add an End-to-End Testing Solution? βΊ No
β Add ESLint for code quality? βΊ Yes
β Add Prettier for code formatting? β¦ Yes
```
### Install dependencies
Run the following command to install Jazz libraries:
```bash
pnpm install jazz-tools jazz-browser jazz-vue
```
### Implement `schema.ts`
Define the schema for your application.
Example schema inside `src/schema.ts` for a todo app:
```typescript
export class ToDoItem extends CoMap {
name = co.string;
completed = co.boolean;
}
export class ToDoList extends CoList.Of(co.ref(ToDoItem)) {}
export class Folder extends CoMap {
name = co.string;
items = co.ref(ToDoList);
}
export class FolderList extends CoList.Of(co.ref(Folder)) {}
export class ToDoAccountRoot extends CoMap {
folders = co.ref(FolderList);
}
export class ToDoAccount extends Account {
profile = co.ref(Profile);
root = co.ref(ToDoAccountRoot);
migrate() {
if (!this._refs.root) {
const group = Group.create({ owner: this });
const firstFolder = Folder.create(
{
name: "Default",
items: ToDoList.create([], { owner: group }),
},
{ owner: group },
);
this.root = ToDoAccountRoot.create(
{
folders: FolderList.create([firstFolder], {
owner: this,
}),
},
{ owner: this },
);
}
}
}
```
### Refactor `main.ts`
Update the `src/main.ts` file to integrate Jazz:
```typescript
declare module "jazz-vue" {
interface Register {
Account: ToDoAccount;
}
}
const RootComponent = defineComponent({
name: "RootComponent",
setup() {
return () => [
h(
JazzProvider,
{
AccountSchema: ToDoAccount,
auth: authMethod.value,
peer: "wss://cloud.jazz.tools/?key=vue-todo-example-jazz@garden.co",
},
{
default: () => h(App),
},
),
];
},
});
const app = createApp(RootComponent);
app.use(router);
app.mount("#app");
```
### Set up `router/index.ts`:
Create a basic Vue router configuration. For example:
```typescript
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "Home",
component: HomeView,
},
],
});
export default router;
```
### Implement `App.vue`
Update the `App.vue` file to include logout functionality:
```typescript
Todo App
{{ me.profile?.name }}
```
## Subscribing to a CoValue
Subscribe to a CoValue inside `src/views/HomeView.vue`:
```typescript