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`
    }),
    roles: i.entity({
      type: i.string().unique(), // We couldn't add this directly to `$users` either
    }),
    todos: i.entity({
      text: 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(
    db.tx.todos[newId]
      .update({ text: newTodo, 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([
    db.tx.profiles[profileId]
      .update({ 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(
  db.tx.comments[commentId]
    .update({ text: 'Hello world' })
    .link({ $users: currentUser.id }),
);

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

// ❌ This will also not work! Cannot create new properties on `$users`
db.transact(db.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',
    },
  },
};