Pick your app

The examples below will be updated with your app ID.

Platform features

Managing users

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.

// instant.perms.ts
import type { InstantRules } from "@instantdb/react";

const rules = {
  $users: {
    allow: {
      view: 'auth.id == data.id',
      create: 'false',
      delete: 'false',
      update: 'false',
    },
  },
} satisfies InstantRules;

export default rules;

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:

// instant.schema.ts
import { i } from '@instantdb/react';

const _schema = i.schema({
  entities: {
    $users: i.entity({
      email: i.any().unique().indexed(),
    }),
    profiles: i.entity({
      nickname: i.string(), // We can't add this directly to `$users`
      userId: i.string().unique(),
    }),
    roles: i.entity({
      type: i.string().unique(), // We couldn't add this directly to `$users` either
    }),
    todos: i.entity({
      text: i.string(),
      userId: i.string(),
      completed: i.boolean(),
    }),
  },
  links: {
    // `$users` is in the reverse direction for all these links!
    todoOwner: {
      forward: { on: 'todos', has: 'one', label: 'owner' },
      reverse: { on: '$users', has: 'many', label: 'todos'},
    },
    userRoles: {
      forward: { on: 'roles', has: 'many', label: 'users' },
      reverse: { on: '$users', has: 'one', label: 'role' },
    },
    userProfiles: {
      forward: { on: 'profiles', has: 'one', label: 'user' },
      reverse: { on: '$users', has: 'one', label: 'profile' },
    },
  },
});

// This helps Typescript display nicer intellisense
type _AppSchema = typeof _schema;
interface AppSchema extends _AppSchema {}
const schema: AppSchema = _schema;

export type { AppSchema };
export default schema;

We created three links todoOwner, userRoles, and userProfiles to link the $users namespace to the todos, roles, and profiles namespaces respectively:

// instant.schema.ts
import { i } from '@instantdb/react';

const _schema = i.schema({
  // ..
  links: {
    // `$users` is in the reverse direction for all these links!
    todoOwner: {
      forward: { on: 'todos', has: 'one', label: 'owner' },
      reverse: { on: '$users', has: 'many', label: 'todos' },
    },
    userRoles: {
      forward: { on: 'roles', has: 'many', label: 'users' },
      reverse: { on: '$users', has: 'one', label: 'role' },
    },
    userProfiles: {
      forward: { on: 'profiles', has: 'one', label: 'user' },
      reverse: { on: '$users', has: 'one', label: 'profile' },
    },
  },
});

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:

// instant.schema.ts
import { i } from '@instantdb/react';

const _schema = i.schema({
  entities: {
    // ...
    profiles: i.entity({
      nickname: i.string(), // We can't add this directly to `$users`
    }),
  },
  // ...
});

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:

// Creates a todo and links the current user as an owner
const addTodo = (newTodo, currentUser) => {
  const newId = id();
  db.transact(
    tx.todos[newId]
      .update({ text: newTodo, userId: currentUser.id, completed: false })
      // Link the todo to the user with the `owner` label we defined in the schema
      .link({ owner: currentUser.id }),
  );
};

// Creates or updates a user profile with a nickname and links it to the
// current user
const updateNick = (newNick, currentUser) => {
  const profileId = lookup('email', currentUser.email);
  db.transact([
    tx.profiles[profileId]
      .update({ userId: currentUser.id, nickname: newNick })
      // Link the profile to the user with the `user` label we defined in the schema
      .link({ user: currentUser.id }),
  ]);
};

If attr creation on the client is enabled, 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.

// Comments is a new namespace! We haven't defined it in the schema.

// ✅ This works!
const commentId = id()
db.transact(
  tx.comments[commentId].update({ text: 'Hello world', userId: currentUser.id })
    .link({ $user: currentUser.id }));

// ❌ This will not work! Cannot create a forward link on the fly
const commentId = id()
db.transact([
  tx.comments[id()].update({ text: 'Hello world', userId: currentUser.id }),
  tx.$users[currentUser.id].link({ comment: commentId }))]);

// ❌ This will also not work! Cannot create new properties on `$users`
db.transact(tx.$users[currentUser.id].update({ nickname: "Alyssa" }))

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:

export default {
  // users perms...
  todos: {
    allow: {
      // owner is the label from the todos namespace to the $users namespace
      update: "auth.id in data.ref('owner.id')",
    },
  },
};

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:

export default {
  // users perms...
  todos: {
    allow: {
      // We traverse the users links directly from the auth object
      update: "data.id in auth.ref('$user.todos.id')",
    },
  },
};

By creating links to $users and leveraging auth.ref, you can expressively build more complex permission rules.

export default {
  // users perms...
  "todos": {
    "bind" : [
      "isAdmin", "'admin' in auth.ref('$user.role.type')",
      "isOwner", "data.id in auth.ref('$user.todos.id')"
    ]
    "allow": {
      // We traverse the users links directly from the auth object
      "update": "isAdmin || isOwner",
    }
  }
};