Schemas
Defining

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 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:

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 for more information.

đź’ˇ

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.

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.

// 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.

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.

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 instead of a schema, you can use the QueryResult type.

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:

triplit schema print -l remote -f file

See CLI docs or run triplit schema print --help for more options.