---
description: Learn how use a schema in your Triplit app to enable Typescript support and data validation.
---

import { Callout } from 'nextra-theme-docs';

# Schemas

## Schemaful vs Schemaless

Providing a schema to Triplit is optional, **but it is recommended** in order to
take advantage of all the features provided by Triplit.

Limitations of schemaless mode include:

- You are limited to exclusively using storing value types that are supported by JSON: string, number, boolean, objects, null.
- If you use Typescript, you will not get type checking for your queries and results.
- [Access control rules](/schemas/rules) are defined in schemas, and thus are not supported in schemaless mode.

## Defining your schema

A schema object defines your collections and the attributes and relationships on those collections. Schemas are defined in Javascript like so:

```typescript
import { Schema as S } from '@triplit/client';
import { TriplitClient, ClientSchema } from '@triplit/client';

const schema = {
  todos: {
    schema: S.Schema({
      id: S.Id(),
      text: S.String(),
      complete: S.Boolean(),
      created_at: S.Date(),
      tags: S.Set(S.String()),
    }),
  },
  users: {
    schema: S.Schema({
      id: S.Id(),
      name: S.String(),
      address: S.Record({
        street: S.String(),
        city: S.String(),
        state: S.String(),
        zip: S.String(),
      }),
    }),
  },
} satisfies ClientSchema;

const client = new TriplitClient({
  schema,
});
```

Passing a schema to the client constructor will override any schema currently stored in your cache. This can cause data corruption, if the new schema is not compatible with existing data in the shape of the old schema. Refer to the [schema management guide](/schemas/updating) for more information.

<Callout>
  By default, your schema file will be created by `triplit init` or `npm create
  triplit-app` in your project directory at `triplit/schema.ts`. If you need to
  save your schema file somewhere else, you can specify that path with the
  `TRIPLIT_SCHEMA_PATH` environmental variable and the Triplit CLI commands will
  refer to it there.
</Callout>

### id

Every collection in Triplit must define an `id` field in its schema. The `S.Id()` data type will generate a random `id` by default upon insertion. If you want to specify the `id` for each entity, you may pass it **as a string** in to the `insert` method as shown below.

```typescript
// assigning the id automatically
await client.insert('todos', {
  text: 'get tortillas',
  complete: false,
  created_at: new Date(),
  tags: new Set([groceries]),
})

// assigning the id manually
await client.insert('todos', {
  id: 'tortillas'
  text: 'get tortillas',
  complete: false,
  created_at: new Date(),
  tags: new Set([groceries]),
})
```

### Getting types from your schema

While the `schema` passed to the client constructor will be used to validate your queries and give you type hinting in any of the client's methods, you may want to extract the types from your schema to use in other parts of your application.

#### `Entity`

You can extract a simple type from your schema with the `Entity` type.

```typescript
import { Entity, ClientSchema } from '@triplit/client';

const schema = {
  todos: {
    schema: S.Schema({
      id: S.Id(),
      text: S.String(),
      complete: S.Boolean(),
      created_at: S.Date(),
      tags: S.Set(S.String()),
    }),
  },
} satisfies ClientSchema;

type Todo = Entity<typeof schema, 'todos'>;
/* 
Todo will be a simple type:
{ 
  id: string, 
  text: string, 
  complete: boolean, 
  created_at: Date, 
  tags: Set<string> 
} 
*/
```

#### `EntityWithSelection`

If you need more advanced types you can use the `EntityWithSelection` type. It expands `Entity` with a `selected` type parameter that can be used to select out specific attributes and an `inclusion` type parameter to add the types of related entities.

```ts
import type { Entity, EntityWithSelection } from '@triplit/client';

const schema = {
  users: {
    schema: S.Schema({
      id: S.Id(),
      name: S.String(),
      posts: S.RelationMany('posts', {
        where: [['authorId', '=', '$1.id']],
      }),
    }),
  },
  posts: {
    schema: S.Schema({
      id: S.Id(),
      text: S.String(),
      authorId: S.String(),
    }),
  },
};

type User = Entity<typeof schema, 'users'>;
/*
type User = {
  id: string;
  name: string;
}
*/

type UserWithPosts = EntityWithSelection<
  typeof schema,
  'users',
  ['name'],
  { posts: true }
>;

/*
type UserWithPosts = {
  name: string;
  posts: Array<{
    id: string;
    text: string;
  }>
}
*/
```

#### `QueryResult`

If you'd like to extract types from a [query](/client/query) instead of a schema, you can use the `QueryResult` type.

```ts
import { client } from './client';
import type { QueryResult } from '@triplit/client';

const userQuery = client
  .query('users')
  .select(['name'].include('posts'))
  .build();

type UserQueryResult = QueryResult<typeof userQuery>;

/*
type UserQueryResult = {
  name: string;
  posts: Array<{
    id: string;
    text: string;
  }>
}
*/
```

### Reading your schema

Your schema is available in your codebase in the `triplit/schema.ts` file. However you may locally edit the schema, or you may not be aware of remote edits that have happened to the schema. To view the current state of the server's schema, run:

```bash
triplit schema print -l remote -f file
```

See [CLI docs](/cli/schema#triplit-schema-print) or run `triplit schema print --help` for more options.
