--- title: Auth description: Instant supports magic code, OAuth, Clerk, and custom auth. --- Instant comes with support for auth. We currently offer , , , and . If you want to build your own flow, you can use the . {% nav-group %} {% nav-button href="/docs/auth/magic-codes" title="Magic Codes" description="Send login codes to your users via email. Removes the need for passwords!" /%} {% nav-button href="/docs/auth/google-oauth" title="Google OAuth" description="We provide flows for Web and React Native to enable Google OAuth for your app." /%} {% nav-button href="/docs/auth/apple" title="Sign In with Apple" description="Sign In to native apps with Apple ID." /%} {% nav-button href="/docs/auth/clerk" title="Clerk" description="Integrate Clerk's auth flow with Instant." /%} {% nav-button href="/docs/backend#custom-auth" title="Custom Auth" description="Integrate your own auth flow with the Admin SDK." /%} {% /nav-group %} --- title: Sign In with Apple description: How to add Sign In with Apple to your Instant app. --- {% nav-default value="web-popup" %} Instant supports Sign In with Apple on the Web and in native applications. {% nav-group %} {% nav-button param="method" value="web-popup" title="Web Popup " description="Use Apple-provided popup to authenticate users" /%} {% nav-button param="method" value="web-redirect" title="Web Redirect" description="Use redirect flow to authenticate users" /%} {% nav-button param="method" value="native" title="React Native" description="Authenticating in React Native app" /%} {% /nav-group %} ## Step 1: Create App ID - Navigate to - Select _Identifiers_ - Click _+_ - _Register a new identifier_ → Select _App IDs_ - _Select a type_ → Select _App_ - _Capabilities_ → _Sign In with Apple_ → Check - Fill in _Bundle ID_ and _Description_ - Click _Register_ ## Step 2: Create Services ID - Navigate to - Click _+_ - _Register a new identifier_ → Select _Services IDs_ - Fill in _Description_ and _Identifier_. You’ll need this _Identifier_ later - Click _Register_ {% conditional param="method" value="web-popup" %} ## Step 3: Configure Services ID - Select newly created Services ID - Enable _Sign In with Apple_ - Click _Configure_ - Select _Primary App ID_ from Step 1 - To _Domains_, add your app domain - To _Return URLs_, add URL of your app where authentication happens - Click _Continue_ → _Save_ {% /conditional %} {% conditional param="method" value="web-redirect" %} ## Step 3: Configure Services ID - Select newly created Services ID - Enable _Sign In with Apple_ - Click _Configure_ - Select _Primary App ID_ from Step 1 - To _Domains_, add `api.instantdb.com` - To _Return URLs_, add `https://api.instantdb.com/runtime/oauth/callback` - Click _Continue_ → _Save_ ## Step 3.5: Generate Private Key - Navigate to - Click _+_ - Fill in _Name_ and _Description_ - Check _Sign in with Apple_ - Configure → select _App ID_ from Step 1 - _Continue_ → _Register_ - Download key file {% /conditional %} {% conditional param="method" value="native" %} ## Step 3: Configure Services ID This step is not needed for Expo. {% /conditional %} ## Step 4: Register your OAuth client with Instant - Go to the Instant dashboard and select _Auth_ tab. - Select _Add Apple Client_ - Select unique _clientName_ - Fill in _Services ID_ from Step 2 {% conditional param="method" value="web-redirect" %} - Fill in _Team ID_ from - Fill in _Key ID_ from Step 3.5 - Fill in _Private Key_ by copying file content from Step 3.5 {% /conditional %} - Click `Add Apple Client` {% conditional param="method" value="web-redirect" %} ## Step 4.5: Whitelist your domain in Instant - In Instant Dashboard, Click _Redirect Origins_ → _Add an origin_ - Add your app’s domain {% /conditional %} {% conditional param="method" value="web-popup" %} ## Step 5: Add Sign In code to your app Add Apple Sign In library to your app: Initialize with `Services ID` from Step 2: Implement `signInPopup` using `clientName` from Step 4: Add Sign In button: {% /conditional %} {% conditional param="method" value="web-redirect" %} ## Step 5: Add Sign In code to your app Create Sign In link using `clientName` from Step 4: Add a link uses `authUrl`: That’s it! {% /conditional %} {% conditional param="method" value="native" %} ## Step 5: Add Sign In code to your app Instant comes with support for . Add dependency: Update `app.json` by adding: Go to Instant dashboard → Auth tab → Redirect Origins → Add an origin. Add `exp://` for development with Expo. Authenticate with Apple and then pass identityToken to Instant along with `clientName` from Step 4: Sign out code: Full example: {% /conditional %} {% /nav-default %} --- title: Clerk description: How to integrate Clerk's auth flow with Instant. --- Instant supports auth with Clerk. ## Setup **Step 1: Configure Clerk** Go to your Clerk dashboard, navigate to , then click the `Edit` button in the `Customize session token` section. Add the email claim to your session token: You can have additional claims as long as the `email` claim is set to `{{user.primary_email_address}}`. ! **Step 2: Get your Clerk Publishable key** On the Clerk dashboard, navigate to , then copy the `Publishable key`. It should start with `pk_`. **Step 3: Register your Clerk Publishable key with your instant app** Go to the Instant dashboard, navigate to the `Auth` tab and add a new clerk app with the publishable key you copied. ## Usage Use Clerk's `getToken` helper to get a session JWT for your signed-in user. Then call Instant's `db.auth.signInWithIdToken` with the JWT and the client name you set on the Instant dashboard. When you call `db.auth.signInWithIdToken`, Instant will verify that the JWT was signed by your Clerk app. If verified, Instant use the email in the JWT's claims to lookup your user or create a new one and create a long-lived session. Be sure to call Instant's `db.auth.signOut` when you want to sign the user out. Here is a full example using clerk's next.js library: --- title: Google OAuth description: How to add Google OAuth to your Instant app. --- {% nav-default value="native" %} Instant supports logging in your users with their Google account. We support flows for Web and React Native. Follow the steps below to get started. **Step 1: Configure OAuth consent screen** Go to the . Click "CONFIGURE CONSENT SCREEN." If you already have a consent screen, you can skip to the next step. Select "External" and click "CREATE". Add your app's name, a support email, and developer contact information. Click "Save and continue". No need to add scopes or test users. Click "Save and continue" for the next screens. Until you reach the "Summary" screen, click "Back to dashboard". **Step 2: Create an OAuth client for Google** From Google Console, click "+ CREATE CREDENTIALS" Select "OAuth client ID" Select "Web application" as the application type. Add `https://api.instantdb.com/runtime/oauth/callback` as an Authorized redirect URI. If you're testing from localhost, **add both `http://localhost`** and `http://localhost:3000` to "Authorized JavaScript origins", replacing `3000` with the port you use. For production, add your website's domain. **Step 3: Register your OAuth client with Instant** Go to the Instant dashboard and select the `Auth` tab for your app. Register a Google client and enter the client id and client secret from the OAuth client that you created. **Step 4: Register your website with Instant** In the `Auth` tab, add the url of the websites where you are using Instant to the Redirect Origins. If you're testing from localhost, add `http://localhost:3000`, replacing `3000` with the port you use. For production, add your website's domain. **Step 5: Add login to your app** The next sections will show you how to use your configured OAuth client with Instant. {% nav-group %} {% nav-button param="method" value="native" title="Native Button " description="Use Google's pre-styled button to sign in. Using this method you can render your custom app name in the consent screen " /%} {% nav-button param="method" value="redirect" title="Redirect flow " description="Easier to integrate, but doesn't let you render your custom app name." /%} {% nav-button param="method" value="rn-webflow" title="React Native" description="Add Google OAuth to your RN app with our webflow integration." /%} {% /nav-group %} {% conditional param="method" value="native" %} ## Native button for Web You can use with Instant. You'll use `db.auth.SignInWithIdToken` to authenticate your user. The benefit of using Google's button is that you can display your app's name in the consent screen. First, make sure that your website is in the list of "Authorized JavaScript origins" for your Google client on the . If you're using React, the easiest way to include the signin button is through the . Include the button and use `db.auth.signInWithIdToken` to complete sign in. Here's a full example If you're not using React or prefer to embed the button yourself, refer to . When creating your button, make sure to set the `data-ux_mode="popup"`. Your `data-callback` function should look like: {% /conditional %} {% conditional param="method" value="redirect" %} ## Redirect flow for Web If you don't want to use the google styled buttons, you can use the redirect flow instead. Simply create an authorization URL via `db.auth.createAuthorizationURL` and then use the url to create a link. Here's a full example: When your users clicks on the link, they'll be redirected to Google to start the OAuth flow and then back to your site. Instant will automatically log them in to your app when they are redirected. {% /conditional %} {% conditional param="method" value="rn-webflow" %} ## Webview flow on React Native Instant comes with support for Expo's AuthSession library. If you haven't already, follow the AuthSession . Next, add the following dependencies: Update your app.json with your scheme: From the Auth tab on the Instant dashboard, add a redirect origin of type "App scheme". For development with expo add `exp://` and your scheme, e.g. `mycoolredirect://`. Now you're ready to add a login button to your expo app. Here's a full example {% /conditional %} {% /nav-default %} --- title: Magic Code Auth description: How to add magic code auth to your Instant app. --- Instant supports a "magic-code" flow for auth. Users provide their email, we send them a login code on your behalf, and they authenticate with your app. Here's how you can do it with react. ## Full Magic Code Example {% callout %} The example below shows how to use magic codes in a React app. If you're looking for an example with vanilla JS, check out this . {% /callout %} Open up your `app/page.tsx` file, and replace the entirety of it with the following code: Go to `localhost:3000`, aand huzzah 🎉 You've got auth. --- **Let's dig deeper.** We created a `Login` component to handle our auth flow. Of note is `auth.sendMagicCode` and `auth.signInWithMagicCode`. On successful validation, Instant's backend will return a user object with a refresh token. The client SDK will then restart the websocket connection with Instant's sync layer and provide the refresh token. When doing `useQuery` or `transact`, the refresh token will be used to hydrate `auth` on the backend during permission checks. On the client, `useAuth` will set `isLoading` to `false` and populate `user` -- huzzah! ## useAuth Use `useAuth` to fetch the current user. Here we guard against loading our `Main` component until a user is logged in ## Send a Magic Code Use `auth.sendMagicCode` to generate a magic code on instant's backend and email it to the user. ## Sign in with Magic Code You can then use `auth.signInWithMagicCode` to authenticate the user with the magic code they provided. ## Sign out Use `auth.signOut` from the client to invalidate the user's refresh token and sign them out.You can also use the admin SDK to sign out the user . ## Get auth For scenarios where you want to know the current auth state without subscribing to changes, you can use `getAuth`. --- title: Instant on the Backend description: How to use Instant on the server with the Admin SDK. --- You can use Instant on the server as well! This can be especially useful for running scripts, custom auth flows, or sensitive application logic. ## Admin SDK We currently offer a javascript library `@instantdb/admin` for using Instant in a non-browser context. This library is similar to our client SDK with a few tweaks. ### init Similar to `@instantdb/react`, you must `init` before doing any queries or writes. Running `init` authenticates you against our admin API. In addition to providing your `appId`, you must also provide your `adminToken`. {% callout type="warning" %} Whereas exposing your `appId` in source control is fine, it's not safe to expose your admin token. Permission checks will not run for queries and writes from our admin API. Be sure to regenerate your token from your dashboard if it accidentally leaks. {% /callout %} ## Reading and Writing Data `query` and `transact` let you read and write data as an admin. ### query In react we use `db.useQuery` to enable "live queries", queries that will automatically update when data changes. In the admin SDK we instead use an async `db.query` function that simply fires a query once and returns a result. ### transact `db.transact` is an async function that behaves nearly identical to `db.transact` from `@instantdb/react`. It returns a `tx-id` on success. ## Schema `init` also accepts a schema argument: If you add a schema, `db.query` and `db.transact` will come with autocompletion and typesafety out of the box. The backend will also use your schema to generate missing attributes. To learn more about writing schemas, head on over to the section. ## Impersonating users When you use the admin SDK, you can make _any_ query or transaction. As an admin, you bypass permissions. But, sometimes you want to make queries on behalf of your users, and would like to respect permissions. You can do this with the `db.asUser` function. ## Retrieve a user As an admin, you can retrieve an app user record by `email`, `id`, or `refresh_token`. You can do this with the `db.auth.getUser` function. ## Delete a user You can also delete an app user record by `email`, `id`, or `refresh_token`. You can do this with the `db.auth.deleteUser` function. Note, this _only_ deletes the user record and any associated data with cascade on delete. If there's additional data you need to clean up you'll need to do it manually: ## Sign Out The `db.auth.signOut` method allows you to log out a user by invalidating any tokens associated with their email. This can be useful when you want to forcibly log out a user from your application: ## Custom Auth You can use the Admin SDK to create your own authentication flows. To implement custom auth flows, you would make one change in your backend, and one change in your frontend. Here's how it would look: ### 1. Backend: db.auth.createToken Create a new `sign-in` endpoint in your backend. This endpoint will use `db.auth.createToken` to generate an authentication token for the user: If a user with this email does not exist, `auth.createToken` will create a user for you. {% callout type="note" %} Right now we require that every user _must_ have an email. If you need to relax this constraint let us know. {% /callout %} ### 2. Frontend: db.auth.signInWithToken Once your frontend calls your `sign-in` endpoint, it can then use the generated token and sign a user in with `db.auth.signInWithToken`. Here's a full example: ## Generating magic codes We support a out of the box. However, if you'd like to use your own email provider to send the code, you can do this with `db.auth.generateMagicCode` function: ## Authenticated Endpoints You can also use the admin SDK to authenticate users in your custom endpoints. This would have two steps: ### 1. Frontend: user.refresh_token In your frontend, the `user` object has a `refresh_token` property. You can pass this token to your endpoint: ### 2. Backend: auth.verifyToken You can then use `auth.verifyToken` to verify the `refresh_token` that was passed in. --- title: Instant CLI description: How to use the Instant CLI to manage schema and permissions. --- The Instant CLI was designed to drive your Instant application entirely from a project's codebase. You can create apps, define your data model, and update your permissions, **all through your terminal**. ## Init To get started, head on over to your project's root repository, and write: This will guide you through picking an Instant app and generate two files for you: - `instant.schema.ts` defines your application's data model. - `instant.perms.ts` defines your permission rules. To learn how to change `instant.schema.ts`, check our . For `instant.perms.ts`, check out the page. ## Push When you're ready to publish your changes to `instant.schema.ts`, run: This will evaluate your schema, compare it with production, and migrate your data model. {% callout %} `push schema` doesn't support _renaming_ or _deleting_ attributes yet. To do this, use the {% /callout %} Similarly, when you change `instant.perms.ts`, you can run: ## Pull Sometimes, you change your schema or rules from your Explorer. If you want to `pull` the latest version of schema and perms for production, write: This will generate new `instant.schema.ts` and `instant.perms.ts` files, based on your production state. ## App ID Whenever you run a CLI command, we look up your app id. You can either provide an app id as an option: Or store it in your `.env` file: As a convenience, apart from `INSTANT_APP_ID`, we also check for: - `NEXT_PUBLIC_INSTANT_APP_ID` for next apps, - `PUBLIC_INSTANT_APP_ID` for svelte apps, - `VITE_INSTANT_APP_ID` for vite apps - `NUXT_PUBLIC_INSTANT_APP_ID` for nuxt apps - `EXPO_PUBLIC_INSTANT_APP_ID` for expo apps ## Where to save files By default, Instant will search for your `instant.schema.ts` and `instant.perms.ts` file in: 1. The `root` directory: `./` 2. The `src` directory: `./src` 3. The `app` directory: `./app` If you'd like to save them in a custom location, you can set the following environment variables: - `INSTANT_SCHEMA_FILE_PATH` sets the location for your `instant.schema.ts` file. - `INSTANT_PERMS_FILE_PATH` sets the location for your `instant.perms.ts` file. ## Authenticating in CI In CI or similar environments, you may want to handle authentication without having to go through a web-based validation step each time. In these cases, you can provide a `INSTANT_CLI_AUTH_TOKEN` environment variable. To obtain a token for later use, run: Instead of saving the token to your local device, the CLI will print it to your console. You can copy this token and provide it as `INSTANT_CLI_AUTH_TOKEN` later in your CI tool. --- title: Devtool description: Use the Instant devtool to inspect your app in development. --- When you load your app in development, you'll notice a little "Instant" Icon show up: {% screenshot src="/img/docs/devtool-pointer.jpg" /%} This is your handy `devtool` shortcut. Once you click it, you'll see a widget that lets you make changes to your app. Use the `Explorer` to change up your data and schema: {% screenshot src="/img/docs/devtool-explorer.png" /%} Or the `Sandbox` to try out different queries and transactions: {% screenshot src="/img/docs/devtool-sandbox.png" /%} ## Changing Positions You can choose where to position your devtool as well. Pass in the `devtool` configuration in `init`: You can set `bottom-left`, `top-left`, `top-right`, `bottom-right`. ## Custom Hosts By default, the devtool only shows up on `localhost`. But you can decide which hosts to show it on too. Pass in the `allowedHosts` option: ## Disabling the devtool If you would like to hide the devtool completely, you can add `devtool: false` in `init`: ## Shortcuts To quickly toggle the window, you can use the shortcut `ctrl` + `shit` + `0` ## Feedback? If you have any feedback, let us know on --- title: Custom email templates and senders description: How to customize magic code emails. --- You can customize all aspects of your Instant app's "magic code" email: the body , subject, sender name, and even the `from` address. ## Dashboard To start, go to your Dashboard's . Click "Custom Magic Code Email", and you're ready to go. ### Variables We provide a handful of variables you can use in both your subject line and body template: - `{code}`, the magic code, e.g. _123456_. Note, this variable is required in both the subject and body. - `{user_email}`, the recipient user's email address, e.g. *happyuser@gmail.com* - `{app_title}`, your app's name Using a variable is as easy as adding the variable's name in curly brackets, e.g. `{variable_name}`. ## Custom sender addresses You can also set Instant's email's `from` and `reply-to` fields to an address on your own domain. If you provide a custom sender address, you'll need to confirm it before we can start delivering from it. Our email partner, Postmark, will send a confirmation to the provided address with a link to verify. Until the address is verified, emails will continue to be sent from Instant's default auth sender . --- title: Getting started pageTitle: Instant - The Modern Firebase. description: Build a realtime app in less than 5 minutes with Instant. --- Instant is the Modern Firebase. With Instant you can easily build realtime and collaborative apps like Notion or Figma. Curious about what it's all about? Try a {% blank-link href="https://instantdb.com/tutorial" label="demo" /%}. Have questions? {% blank-link href="https://discord.com/invite/VU53p7uQcE" label="Join us on discord!" /%} And if you're ready, follow the quick start below to **build a live app in less than 5 minutes!** ## Quick start To use Instant in a brand new project, fire up your terminal and run the following: Now open up `app/page.tsx` in your favorite editor and replace the entirety of the file with the following code. Go to `localhost:3000`, aand huzzah 🎉 You've got your first Instant web app running! Check out the section to learn more about how to use Instant :) --- title: Initializing Instant description: How to integrate Instant with your app. --- ## Basic Initialization The first step to using Instant in your app is to call `init`. Here is a simple example at the root of your app. With that, you can use `db` to , , , and more! ## Flexible Initialization Instant maintains a single connection regardless of where or how many times you call `init` with the same app ID. This means you can safely call `init` multiple times without worrying about creating multiple connections or performance overhead. However we do recommend the pattern of exporting a reference from a utility file like so: ## Typesafety If you're using typescript, `init` accepts a `schema` argument. Adding a schema provides auto-completion and typesafety for your queries and transactions. To learn more about writing schemas, head on over to the section. --- title: Writing data description: How to write data with Instant using InstaML. --- Instant uses a **Firebase-inspired** interface for mutations. We call our mutation language **InstaML** ## Update data We use the `update` action to create entities. This creates a new `goal` with the following properties: - It's identified by a randomly generated id via the `id` function. - It has an attribute `title` with value `eat`. Similar to NoSQL, you don't need to use the same schema for each entity in a namespace. After creating the previous goal you can run the following: You can store `strings`, `numbers`, `booleans`, `arrays`, and `objects` as values. You can also generate values via functions. Below is an example for picking a random goal title. --- The `update` action is also used for updating entities. Suppose we had created the following goal We eat some food and decide to update the goal. We can do that like so: This will only update the value of the `lastTimeEaten` attribute for entity `eat`. ## Merge data When you `update` an attribute, you overwrite it. This is fine for updating values of strings, numbers, and booleans. But if you use `update` to overwrite json objects you may encounter two problems: 1. You lose any data you didn't specify. 2. You risk clobbering over changes made by other clients. For example, imagine we had a `game` entity, that stored a `state` of favorite colors: To make working with deeply-nested, document-style JSON values a breeze, we created `merge`. Similar to , `merge` allows you to specify the slice of data you want to update: `merge` only merges objects. Calling `merge` on **arrays, numbers, or booleans** will overwrite the values. Sometimes you may want to remove keys from a nested object. You can do so by calling `merge` with a key set to `null` or `undefined`. This will remove the corresponding property from the object. ## Delete data The `delete` action is used for deleting entities. You can generate an array of `delete` txs to delete all entities in a namespace Calling `delete` on an entity also deletes its associations. So no need to worry about cleaning up previously created links. ## Link data `link` is used to create associations. Suppose we create a `goal` and a `todo`. We can associate `healthId` with `workoutId` like so: We could have done all this in one `transact` too via chaining transaction chunks. You can specify multiple ids in one `link` as well: Links are bi-directional. Say we link `healthId` to `workoutId` We can query associations in both directions ## Unlink data Links can be removed via `unlink.` This removes links in both directions. Unlinking can be done in either direction so unlinking `workoutId` from `healthId` would have the same effect. We can `unlink` multiple ids too: ## Lookup by unique attribute If your entity has a unique attribute, you can use `lookup` in place of the id to perform updates. The `lookup` function takes the attribute as its first argument and the unique attribute value as its second argument. When it is used in a transaction, the updates will be applied to the entity that has the unique value. If no entity has the value, then a new entity with a random id will be created with the value. It can be used with `update`, `delete`, `merge`, `link`, and `unlink`. ## Lookups in links When used with links, it can also be used in place of the linked entity's id. ## Transacts are atomic When you call `db.transact`, all the transactions are committed atomically. If any of the transactions fail, none of them will be committed. ## Typesafety By default, `db.transact` is permissive. When you save data, we'll create missing attributes for you: As your app grows, you may want to start enforcing types. When you're ready, you can start using a : If your schema includes a `todos.dueDate` for example: Instant will enforce that `todos.dueDate` are actually dates, and you'll get some nice intellisense to boot: {% screenshot src="/img/docs/instaml-due-date.png" /%} Instant also comes with a few utility types, which can help you write abstractions over `transact`. For example, say you wanted to write a custom `update` function: You can use the `UpdateParams` utility to make sure arguments follow the schema: And the `LinkParams` utility do the same for links: To learn more about writing schemas, check out the section. ## Batching transactions If you have a large number of transactions to commit, you'll want to batch them to avoid hitting transaction limits and time outs. Suppose we want to create 3000 goals. Here's how we can batch them into 30 transactions of 100 goals each. ## Using the tx proxy object `db.tx` is a which creates transaction chunks to be committed via `db.transact`. It follows the format - `NAMESPACE_LABEL` refers to the namespace to commit - `ENTITY_IDENTIFIER` is the id to look up in the namespace. This id must be a uuid and unique to the namespace. You can use the `id` function to generate a uuid for convenience. - `ACTION` is one of `update`, `merge`, `delete`, `link`, `unlink` - `ACTION_SPECIFIC_DATA` depends on the action - `update` takes in an object of information to commit - `merge` takes in an object to deep merge with the existing data - `delete` is the only action that doesn't take in any data, - `link` and `unlink` takes an object of label-entity pairs to create/delete associations --- title: Reading data description: How to read data with Instant using InstaQL. --- Instant uses a declarative syntax for querying. It's like GraphQL without the configuration. Here's how you can query data with **InstaQL.** ## Fetch namespace One of the simplest queries you can write is to simply get all entities of a namespace. Inspecting `data`, we'll see: For comparison, the SQL equivalent of this would be something like: ## Fetch multiple namespaces You can fetch multiple namespaces at once: We will now see data for both namespaces. The equivalent of this in SQL would be to write two separate queries. ## Fetch a specific entity If you want to filter entities, you can use the `where` keyword. Here we fetch a specific goal The SQL equivalent would be: ## Fetch associations We can fetch goals and their related todos. `goals` would now include nested `todos` ### Comparing with SQL The SQL equivalent for this would be something along the lines of: Notice the complexity of this SQL query. Although fetching associations in SQL is straightforward via `JOIN`, marshalling the results in a nested structure via SQL is tricky. An alternative approach would be to write two straight-forward queries and then marshall the data on the client. Now compare these two approaches with `InstaQL` Modern applications often need to render nested relations, `InstaQL` really starts to shine for these use cases. ## Fetch specific associations ### A) Fetch associations for filtered namespace We can fetch a specific entity in a namespace as well as it's related associations. Which returns ### B) Filter namespace by associated values We can filter namespaces **by their associations** Returns ### C) Filter associations We can also filter associated data. This will return goals and filtered todos --- {% callout %} Notice the difference between these three cases. - A) Fetched all todos for goal with id `health` - B) Filtered goals with a least one todo titled `Code a bunch` - C) Fetched all goals and filtered associated todos by title `Go on a run` {% /callout %} --- ## Inverse Associations Associations are also available in the reverse order. ## Defer queries You can also defer queries until a condition is met. This is useful when you need to wait for some data to be available before you can run your query. Here's an example of deferring a fetch for todos until a user is logged in. ## Pagination You can limit the number of items from a top level namespace by adding a `limit` to the option map: Instant supports both offset-based and cursor-based pagination for top-level namespaces. ### Offset To get the next page, you can use an offset: In a React application, your offset-based pagination code might look something like this: ### Cursors You can also get the next page with the `endCursor` returned in the `pageInfo` map from the previous result: To get the previous page, use the `startCursor` in the `before` field of the option map and ask for the `last` items: In a React application, your cursor-based pagination code might look something like this: ### Ordering The default ordering is by the time the objects were created, in ascending order. You can change the order with the `order` key in the option map for top-level namespaces: The `serverCreatedAt` field is a reserved key that orders by the time that the object was first persisted on the Instant backend. It can take the value 'asc' or 'desc'. You can also order by any attribute that is indexed and has a checked type. {% callout %} Add indexes and checked types to your attributes from the or from the . {% /callout %} ## Advanced filtering ### Multiple `where` conditions The `where` clause supports multiple keys which will filter entities that match all of the conditions. ### And The `where` clause supports `and` queries which are useful when you want to filter entities that match multiple associated values. In this example we want to find goals that have todos with the titles `Drink protein` and `Go on a run` ### OR The `where` clause supports `or` queries that will filter entities that match any of the clauses in the provided list: ### $in The `where` clause supports `$in` queries that will filter entities that match any of the items in the provided list. You can think of this as a shorthand for `or` on a single key. ### Comparison operators The `where` clause supports comparison operators on fields that are indexed and have checked types. {% callout %} Add indexes and checked types to your attributes from the or from the . {% /callout %} | Operator | Description | JS equivalent | | :------: | :----------------------: | :-----------: | | `$gt` | greater than | `>` | | `$lt` | less than | `<` | | `$gte` | greater than or equal to | `>=` | | `$lte` | less than or equal to | `<=` | Dates can be stored as timestamps `) or as ISO 8601 strings )`) and can be queried in the same formats: If you try to use comparison operators on data that isn't indexed and type-checked, you'll get an error: ### $not The `where` clause supports `$not` queries that will return entities that don't match the provided value for the field, including entities where the field is null or undefined. ### $isNull The `where` clause supports `$isNull` queries that will filters entities by whether the field value is either null or undefined. Set `$isNull` to `true` to return entities where where the field is null or undefined. Set `$isNull` to `false` to return entities where the field is not null and not undefined. ### $like The `where` clause supports `$like` on fields that are indexed with a checked `string` type. `$like` queries will return entities that match a **case sensitive** substring of the provided value for the field. For **case insensitive** matching use `$ilike` in place of `$like`. Here's how you can do queries like `startsWith`, `endsWith` and `includes`. | Example | Description | JS equivalent | | :-----------------------: | :-------------------: | :-----------: | | `{ $like: "Get%" }` | Starts with 'Get' | `startsWith` | | `{ $like: "%promoted!" }` | Ends with 'promoted!' | `endsWith` | | `{ $like: "%fit%" }` | Contains 'fit' | `includes` | Here's how you can use `$like` to find all goals that end with the word "promoted!" You can use `$like` in nested queries as well Returns Case-insensitive matching with `$ilike`: ## Select fields An InstaQL query will fetch all fields for each object. If you prefer to select the specific fields that you want your query to return, use the `fields` param: `fields` also works with nested relations: Using `fields` can be useful for performance optimization. It reduces the amount of data that needs to be transferred from the server and minimizes the number of re-renders in your React application if there are no changes to your selected fields. {% callout type="warning" %} Using `fields` doesn't restrict a client from doing a full query. If you have sensitive data on your entities that you don't want to expose you'll want to use and potentially to restrict access. {% /callout %} ## Typesafety By default, `db.useQuery` is permissive. You don't have to tell us your schema upfront, and you can write any kind of query: As your app grows, you may want to start enforcing types. When you're ready you can write a : If your schema includes `goals` and `todos` for example: ### Intellisense Instant will start giving you intellisense for your queries. For example, if you're querying for goals, you'll see that only `todos` can be associated: {% screenshot src="/img/docs/instaql-todos-goals-autocomplete.png" /%} And if you hover over `data`, you'll see the actual typed output of your query: {% screenshot src="/img/docs/instaql-data-intellisense.png" /%} ### Utility Types Instant also comes with some utility types to help you use your schema in TypeScript. For example, you could define your `query` upfront: Or you can define your result type: Or you can extract a particular entity: You can specify links relative to your entity: To learn more about writing schemas, check out the section. ## Query once Sometimes, you don't want a subscription, and just want to fetch data once. For example, you might want to fetch data before rendering a page or check whether a user name is available. In these cases, you can use `queryOnce` instead of `useQuery`. `queryOnce` returns a promise that resolves with the data once the query is complete. Unlike `useQuery`, `queryOnce` will throw an error if the user is offline. This is because `queryOnce` is intended for use cases where you need the most up-to-date data. You can also do pagination with `queryOnce`: --- title: Modeling data description: How to model data with Instant's schema. --- In this section we’ll learn how to model data using Instant's schema. By the end of this document you’ll know how to: - Create namespaces and attributes - Add indexes and unique constraints - Model relationships - Lock down your schema for production We’ll build a micro-blog to illustrate; we'll have authors, posts, comments, and tags. ## Schema as Code With Instant you can define your schema and your permissions in code. If you haven't already, use the to generate an `instant.schema.ts`, and a `instant.perms.ts` file: The CLI will guide you through picking an Instant app and generate these files for you. ## instant.schema.ts Now we can define the data model for our blog! Open `instant.schema.ts`, and paste the following: Let's unpack what we just wrote. There are three core building blocks to model data with Instant: **Namespaces**, **Attributes**, and **Links**. ## 1) Namespaces Namespaces are equivelant to "tables" in relational databases or "collections" in NoSQL. In our case, these are: `$users`, `profiles`, `posts`, `comments`, and `tags`. They're all defined in the `entities` section: ## 2) Attributes Attributes are properties associated with namespaces. These are equivelant to a "column" in relational databases or a "field" in NoSQL. For the `posts` entity, we have the `title`, `body`, and `createdAt` attributes: ### Typing attributes Attributes can be typed as `i.string`, `i.number`, `i.boolean`, `i.date`, `i.json`, or `i.any`. {% callout %} `i.date` accepts dates as either a numeric timestamp or an ISO 8601 string. `JSON.stringify)` will return an ISO 8601 string. {% /callout %} When you type `posts.title` as a `string`: Instant will _make sure_ that all `title` attributes are strings, and you'll get the proper typescript hints to boot! ### Unique constraints Sometimes you'll want to introduce a unique constraint. For example, say we wanted to add friendly URL's to posts. We could introduce a `slug` attribute: Since we're going to use post slugs in URLs, we'll want to make sure that no two posts can have the same slug. If we mark `slug` as `unique`, _Instant will guarantee this constraint for us_. Plus unique attributes come with their own special index. This means that if you use a unique attribute inside a query, we can fetch the object quickly: ### Indexing attributes Speaking of fast queries, let's take a look at one: What if we wanted to query for a post that was published at a particular date? Here's a query to get posts that were published during SpaceX's chopstick launch: This would work, but the more posts we create, the slower the query would get. We'd have to scan every post and compare the `createdAt` date. To make this query faster, we can index `createdAt`: As it says on the tin, this command tells Instant to index the `createdAt` field, which lets us quickly look up entities by this attribute. ## 3) Links Links connect two namespaces together. When you define a link, you define it both in the 'forward', and the 'reverse' direction. For example: This links `posts` and `profiles` together: - `posts.author` links to _one_ `profiles` entity - `profiles.authoredPosts` links back to _many_ `posts` entities. Since links are defined in both directions, you can query in both directions too: Links can have one of four relationship types: `many-to-many`, `many-to-one`, `one-to-many`, and `one-to-one` Our micro-blog example has the following relationship types: - **One-to-one** between `profiles` and `$users` - **One-to-many** between `posts` and `profiles` - **One-to-many** between `comments` and `posts` - **One-to-many** between `comments` and `profiles` - **Many-to-many** between `posts` and `tags` ### Cascade Delete Links defined with `has: "one"` can set `onDelete: "cascade"`. In this case, when the profile entity is deleted, all post entities will be deleted too: Without `onDelete: "cascade"`, deleting a profile would simply delete the links but not delete the underlying posts. If you prefer to model links in other direction, you can do it, too: ## Publishing your schema Now that you have your schema, you can use the CLI to `push` it to your app: The CLI will look at your app in production, show you the new columns you'd create, and run the changes for you! {% ansi %} {% /ansi %} ## Use schema for typesafety You can also use your schema inside `init`: When you do this, all and will come with typesafety out of the box. {% callout %} If you haven't used the CLI to push your schema yet, no problem. Any time you write `transact`, we'll automatically create missing entities for you. {% /callout %} ## Update or Delete attributes You can always modify or delete attributes after creating them. **You can't use the CLI to do this yet, but you can use the dashboard.** Say we wanted to rename `posts.createdAt` to `posts.publishedAt`: 1. Go to your 2. Click "Explorer" 3. Click "posts" 4. Click "Edit Schema" 5. Click `createdAt` You'll see a modal that you can use to rename the attribute, index it, or delete it: {% screenshot src="/img/docs/modeling-data-rename-attr.png" /%} ## Secure your schema with permissions In the earlier sections we mentioned that new `entities` and `attributes` can be created on the fly when you call `transact`. This can be useful for development, but you may not want this in production. To prevent changes to your schema on the fly, simply add these permissions to your app. Once you push these permissions to production: {% ansi %} {% /ansi %} You'll still be able to make changes in the explorer or with the CLI, but client-side transactions that try to modify your schema will fail. This means your schema is safe from unwanted changes! --- **If you've made it this far, congratulations! You should now be able to fully customize and lock down your data model. Huzzah!** --- title: Patterns description: Common patterns for working with InstantDB. --- Below are some common patterns for working with InstantDB. We'll add more patterns over time and if you have a pattern you'd like to share, please feel free to submit a PR for this page. ## You can expose your app id to the client. Similar to Firebase, the app id is a unique identifier for your application. If you want to secure your data, you'll want to add for the app. ## Restrict creating new attributes. When your ready to lock down your schema, you can restrict creating a new attribute by adding this to your app's This will prevent any new attributes from being created. ## Attribute level permissions When you query a namespace, it will return all the attributes for an entity. You can use the clause to restrict which attributes are returned from the server but this will not prevent a client from doing another query to get the full entity. At the moment InstantDB does not support attribute level permissions. This is something we are actively thinking about though! In the meantime you can work around this by splitting your entities into multiple namespaces. This way you can set separate permissions for private data. ## Find entities with no links. If you want to find entities that have no links, you can use the `$isNull` query filter. For example, if you want to find all posts that are not linked to an author you can do ## Setting limits via permissions. If you want to limit the number of entities a user can create, you can do so via permissions. Here's an example of limiting a user to creating at most 2 todos. First the : Then the : ## Listen to InstantDB connection status. Sometimes you want to let clients know when they are connected or disconnected to the DB. You can use `db.subscribeConnectionStatus` in vanilla JS or `db.useConnectionStatus` in React to listen to connection changes --- title: Permissions description: How to secure your data with Instant's Rule Language. --- To secure user data, you can use Instant’s Rule Language. Our rule language takes inspiration from Rails’ ActiveRecord, Google’s CEL, and JSON. Here’s an example ruleset below You can manage permissions via configuration files or through the Instant dashboard. ## Permissions as code With Instant you can define your permissions in code. If you haven't already, use the to generate an `instant.perms.ts` file: The CLI will guide you through picking an Instant app and generate these files for you. Once you've made changes to `instant.perms.ts`, you can use the CLI to push those changes to production: ## Permissions in the dashboard For each app in your dashboard, you’ll see a permissions editor. Permissions are expressed as JSON. Each top level key represents one of your namespaces — for example `goals`, `todos`, and the like. There is also a special top-level key `attrs` for defining permissions on creating new types of namespaces and attributes. ## Namespaces For each namespace you can define `allow` rules for `view`, `create`, `update`, `delete`. Rules must be boolean expressions. If a rule is not set then by default it evaluates to true. The following three rulesets are all equivalent In this example we explicitly set each action for `todos` to true In this example we explicitly set `view` to be true. However, all the remaining actions for `todo` also default to true. In this example we set no rules, and thus all permission checks pass. {% callout type="warning" %} When you start developing you probably won't worry about permissions. However, once you start shipping your app to users you will want to secure their data! {% /callout %} ### View `view` rules are evaluated when doing `db.useQuery`. On the backend every object that satisfies a query will run through the `view` rule before being passed back to the client. This means as a developer you can ensure that no matter what query a user executes, they’ll _only_ see data that they are allowed to see. ### Create, Update, Delete Similarly, for each object in a transaction, we make sure to evaluate the respective `create`, `update`, and `delete` rule. Transactions will fail if a user does not have adequate permission. ### Default permissions By default, all permissions are considered to be `"true"`. To change that, use `"$default"` key. This: is equivalent to this: Specific keys can override defaults: You can use `$default` as the namespace: Finally, the ultimate default: ## Attrs Attrs are a special kind of namespace for creating new types of data on the fly. Currently we only support creating attrs. During development you likely don't need to lock this rule down, but once you ship you will likely want to set this permission to `false` Suppose our data model looks like this And we have a rules defined as Then we could create goals with existing attr types: But we would not be able to create goals with new attr types: ## CEL expressions Inside each rule, you can write CEL code that evaluates to either `true` or `false`. The above example shows a taste of the kind of rules you can write :) ### data `data` refers to the object you have saved. This will be populated when used for `view`, `create`, `update`, and `delete` rules ### newData In `update`, you'll also have access to `newData`. This refers to the changes that are being made to the object. ### bind `bind` allows you to alias logic. The following are equivalent `bind` is useful for not repeating yourself and tidying up rules ### ref You can also refer to relations in your permission checks. This rule restricts delete to only succeed on todos associated with a specific user email. `ref` works on the `auth` object too. Here's how you could restrict `deletes` to users with the 'admin' role: See to learn more about that. ### ruleParams Imagine you have a `documents` namespace, and want to implement a rule like _"Only people who know my document's id can access it."_ You can use `ruleParams` to write that rule. `ruleParams` let you pass extra options to your queries and transactions. For example, pass a `knownDocId` param to our query: Or to your transactions: And then use it in your permission rules: With that, you've implemented the rule _"Only people who know my document's id can access it."_! **Here are some more patterns** If you want to: access a document and _all related comments_ by one `knownDocId`: Or, if you want to allow multiple documents: To create a “share links” feature, where you have multiple links to the same doc, you can create a separate namespace: Or if you want to separate “view links” from “edit links”, you can use two namespaces like this: --- title: Presence, Cursors, and Activity description: How to add epehemeral features like presence and cursors to your Instant app. --- Sometimes you want to show real-time updates to users without persisting the data to your database. Common scenarios include: - Shared cursors in a collaborative whiteboard like Figma - Who's online in a document editor like Google Docs - Typing indicators in chat apps like Discord - Live reactions in a video streaming app like Twitch Instant provides three primitives for quickly building these ephemeral experiences: rooms, presence, and topics. **Rooms** A room represents a temporary context for realtime events. Users in the same room will receive updates from every other user in that room. **Presence** Presence is an object that each peer shares with every other peer. When a user updates their presence, it's instantly replicated to all users in that room. Presence persists throughout the remainder of a user's connection, and is automatically cleaned up when a user leaves the room You can use presence to build features like "who's online." Instant's cursor and typing indicator are both built on top of the presence API. **Topics** Topics have "fire and forget" semantics, and are better suited for data that don't need any sort of persistence. When a user publishes a topic, a callback is fired for every other user in the room listening for that topic. You can use topics to build features like "live reactions." The real-time emoji button panel on Instant's homepage is built using the topics API. **Transact vs. Ephemeral** You may be thinking when would I use `transact` vs `presence` vs `topics`? Here's a simple breakdown: - Use `transact` when you need to persist data to the db. For example, when a user sends a message in a chat app. - Use `presence` when you need to persist data in a room but not to the db. For example, showing who's currently viewing a document. - Use `topics` when you need to broadcast data to a room, but don't need to persist it. For example, sending a live reaction to a video stream. ## Setup To obtain a room reference, call `db.room` ## Typesafety By default rooms accept any kind of data. However, you can enforce typesafety with a schema: Here's how we could add typesafety to our `chat` rooms: Once you've updated your schema, you'll start seeing types in your intellisense: {% screenshot src="/img/docs/presence-intellisence.png" /%} ## Presence One common use case for presence is to show who's online. Instant's `usePresence` is similar in feel to `useState`. It returns an object containing the current user's presence state, the presence state of every other user in the room, and a function to update the current user's presence. `publishPresence` is similar to React's `setState`, and will merge the current and new presence objects. `usePresence` accepts a second parameter to select specific slices of user's presence object. You may also specify an array of `peers` and a `user` flag to further constrain the output. If you wanted a "write-only" hook, it would look like this: ## Topics Instant provides 2 hooks for sending and handling events for a given topic. `usePublishTopic` returns a function you can call to publish an event, and `useTopicEffect` will be called each time a peer in the same room publishes a topic event. Here's a live reaction feature using topics. You can also play with it live on ## Cursors and Typing Indicators We wanted to make adding real-time features to your apps as simple as possible, so we shipped our React library with 2 drop-in utilities: `Cursors` and `useTypingIndicator`. ### Cursors Adding multiplayer cursors to your app is as simple as importing our `` component! You can provide a `renderCursor` function to return your own custom cursor component. You can render multiple cursor spaces. For instance, imagine you're building a screen with multiple tabs. You want to only show cursors on the same tab as the current user. You can provide each `` element with their own `spaceId`. ### Typing indicators `useTypingIndicator` is a small utility useful for building inputs for chat-style apps. You can use this hook to show things like " is typing..." in your chat app. --- title: Showcase description: Real world apps built with Instant. --- ## Sample Apps Here are some sample apps showing how to use Instant to build a real app. - - collaborative drawing app built with Instant. - - simple chat app with presence, typing indicators, and reactions!. - - React Native app for managing ceramic glazes - - casual multiplayer game built with React Native. ## Real World Apps Here are some apps in production that are powered by Instant. - - Palette is a modern, all-in-one project management app for studios & digital artists 🎨 - - Simplify your goals and get things done with mentor, your personal assistant - - A high-quality, no-frills, modern spreadsheet ## More examples Are you looking for more examples? Do you want to contribute your app to this list? Let us know on or --- title: Getting started with React Native description: How to use Instant with React Native --- You can use Instant in React Native projects too! Below is an example using Expo. Open up your terminal and do the following: Now open up `app//index.tsx` in your favorite editor and replace the entirety of the file with the following code. If you haven't already, install the Expo Go app on iOS or Android. Once you have that installed you can run the app from your terminal. Scan the QR code with your phone and follow the instructions on the screen :) Huzzah 🎉 You've got your first React Native Instant app running! Check out the section to learn more about how to use Instant! --- title: Getting started with Vanilla JS description: How to use Instant with Vanilla JS --- You can use Instant with plain ol' Javascript/Typescript too. You may find this helpful to integrate Instant with a framework that doesn't have an official SDK yet. To use Instant in a brand new project fire up your terminal set up a new project with Vite. Now open up `src/main.ts` in your favorite editor and replace the entirety of the file with the following code. Go to `localhost:5173` and follow the final instruction to load the app! Huzzah 🎉 You've got your first Instant web app running! Check out the section to learn more about how to use Instant :) --- title: Storage description: How to upload and serve files with Instant. --- Instant Storage makes it simple to upload and serve files for your app. You can store images, videos, documents, and any other file type. ## Storage quick start Let's build a full example of how to upload and display a grid of images Initialize your schema and permissions via the Now open `instant.schema.ts` and replace the contents with the following code. Similarly open `instant.perms.ts` and replace the contents with the following Push up both the schema and permissions to your Instant app with the following command And then replace the contents of `app/page.tsx` with the following code. With your schema, permissions, and application code set, you can now run your app! Go to `localhost:3000`, and you should see a simple image feed where you can upload and delete images! ## Storage client SDK Below you'll find a more detailed guide on how to use the Storage API from react. ### Upload files Use `db.storage.uploadFile` to upload a file. - `path` determines where the file will be stored, and can be used with permissions to restrict access to certain files. - `file` should be a type, which will likely come from a . - `opts` is optional and can be used to set the `contentType` and `contentDisposition` headers for the file. ### Overwrite files If the `path` already exists in your storage directory, it will be overwritten! If you don't want to overwrite files, you'll need to ensure that each file has a unique path. ### View files You can retrieve files by querying the `$files` namespace. You can use query filters and associations as you would with any other namespace to filter and sort your files. ### Delete files Use `db.storage.delete` to delete a file. ### Link files Use links to associate files with other entities in your schema. Similar to `$users`, links on `$files` can only be created in the **reverse direction.** for a more detailed example showing how you may leverage links to implement an avatar upload feature ## Using Storage with React Native The SDK expects a `File` object. In React Native the built-in `fetch` function can be used to construct a `File`, then you can pass that to the `uploadFile` metod. Example: ## Storage admin SDK The Admin SDK offers a similar API for managing storage on the server. Permission checks are not enforced when using the Admin SDK, so you can use it to manage files without worrying about authentication. ### Uploading files Once again, we use the `db.storage.uploadFile` function to upload a file on the backend. Note that unlike our browser SDK, the `file` argument must be a `Buffer`. In this case you'll likely want to at least specify the `contentType` in the options otherwise the default content-type will be `application/octet-stream`. ### View Files Retrieving files is similar to the client SDK, but we use `db.query` instead of `db.useQuery`. ### Delete files There are two ways to delete files with the admin SDK: - `db.storage.delete` - `db.storage.deleteMany` These allow you to either delete a single file, or bulk delete multiple files at a time. ## Permissions By default, Storage permissions are disabled. This means that until you explicitly set permissions, no uploads or downloads will be possible. - _create_ permissions enable uploading `$files` - _view_ permissions enable viewing `$files` - _delete_ permissions enable deleting `$files` - _view_ permissions on `$files` and _update_ permisssions on the forward entity enabling linking and unlinking `$files` In your permissions rules, you can use `auth` to access the currently authenticated user, and `data` to access the file metadata. At the moment, the only available file metadata is `data.path`, which represents the file's path in Storage. Here are some example permissions Allow anyone to upload and retrieve files : Allow all authenticated users to view and upload files: Authenticated users may only upload and view files from their own subdirectory: --- title: App teams description: How to manage your Instant app's team members and their roles. --- Apps with a Pro subscription can be managed by multiple users. To add team members to your app, head on over to the . ## Roles App team members can have one of three roles: collaborator, admin or owner. #### Collaborators - Can view the Explorer, update Permissions, and configure Auth. #### Admins - Can invite other team members. #### Owners - Can access the Billing tab. - Can regenerate the app's admin tokens. - Can delete their app. ## Invites #### Inviting a team member A pro app's admin or owner simply needs to navigate to the and click "Invite a team member". This will open a dialog that accepts an email and role. This will send an email with instructions to the specified address. #### Accepting an invite Once an invited user signs up for Instant, they can access the where they can accept or decline the invite. --- title: Managing users description: How to manage users in your Instant app. --- ## See users in your app You can manage users in your app using the `$users` namespace. This namespace is automatically created when you create an app. You'll see the `$users` namespace in the `Explorer` tab with all the users in your app! ## Querying users The `$users` namespace can be queried like any normal namespace. However, we've set some default permissions so that only a logged-in user can view their own data. Right now `$users` is a read-only namespace. You can override the `view` permission to whatever you like, but `create`, `delete`, and `update` are restricted. ## Adding properties Although you cannot directly add properties to the `$users` namespace, you can create links to other namespaces. Here is an example of a schema for a todo app that has users, roles, profiles, and todos: ### Links We created three links `todoOwner`, `userRoles`, and `userProfiles` to link the `$users` namespace to the `todos`, `roles`, and `profiles` namespaces respectively: Notice that the `$users` namespace is in the reverse direction for all links. If you try to create a link with `$users` in the forward direction, you'll get an error. ### Attributes Now take a look at the `profiles` namespace: You may be wondering why we didn't add `nickname` directly to the `$users` namespace. This is because the `$users` namespace is read-only and we cannot add properties to it. If you want to add additional properties to a user, you'll need to create a new namespace and link it to `$users`. --- Once done, you can include user information in the client like so: If attr creation on the client , you can also create new links without having to define them in the schema. In this case you can only link to `$users` and not from `$users`. ## User permissions You can reference the `$users` namespace in your permission rules just like a normal namespace. For example, you can restrict a user to only update their own todos like so: You can also traverse the `$users` namespace directly from the `auth` object via `auth.ref`. When using `auth.ref` the arg must start with `$user`. Here's the equivalent rule to the one above using `auth.ref`: By creating links to `$users` and leveraging `auth.ref`, you can expressively build more complex permission rules.