Typesync is an open-source schema management tool that simplifies managing Firestore databases. Typesync allows you to maintain a single source of truth for your Firestore architecture in a special schema. With this schema in place, you can seamlessly auto-generate type definitions for multiple platforms like TypeScript, Swift, Python, and more using the CLI tool.

Typesync keeps your database and application code consistent and up-to-date at all times. In addition to type definitions, it lets you generate other useful things like Security Rules, boilerplate code for Cloud Functions, and documentation for your data models.

Single source of truth for your Firestore architecture

Typesync in 30 seconds

You define your Firestore schema in a collection of YAML/JSON files. The Typesync CLI reads these files and produces ready-to-use Firestore model definitions for all the languages and platforms you work with.

In the example below, we define our models in models.yml.

models.yml
# yaml-language-server: $schema=https://schema.typesync.org/v0.6.json

UserRole:
  model: alias
  docs: Represents a user's role within a project.
  type:
    type: enum
    members:
      - label: Owner
        value: owner
      - label: Admin
        value: admin
      - label: Member
        value: member

User:
  model: document
  docs: Represents a user that belongs to a project.
  type:
    type: object
    fields:
      username:
        type: string
        docs: A string that uniquely identifies the user within a project.
      role:
        type: UserRole
      website_url:
        type: string
        optional: true
      created_at:
        type: timestamp

We then run the following commands to generate type definitions for four different environments and type validators for Security Rules:

typesync generate-ts --target firebase@10 --definition models.yml # ... other options

Typesync generates the following files:

import type * as firestore from 'firebase/firestore';

/** Represents a user's role within a project. */
export type UserRole = 'owner' | 'admin' | 'member';

/** Represents a user that belongs to a project. */
export interface User {
  /** A string that uniquely identifies the user within a project. */
  username: string;
  role: UserRole;
  website_url?: string;
  created_at: firestore.Timestamp;
}

Once we have the generated models, we can import and use them in our application code. As for Security Rules, we just need to deploy them to Firestore.

import {
  getFirestore,
  doc,
  getDoc,
  collection,
  type CollectionReference,
} from "firebase/firestore";
import type { User } from "./models";

const firestore = getFirestore();
const usersColRef = collection(firestore, "users") as CollectionReference<User>;
const userDocRef = doc(usersColRef, "adam");
const userSnap = await getDoc(userDocRef);
const user = userSnap.data(); // is a User object

Design Goals

Typesync is built on three core principles that guide its design and functionality.

1. Predictability

Typesync values straightforward operation over complexity. It doesn’t try to outsmart you by making implicit assumptions. There are no “gotchas”, no hidden configurations. When faced with bad input, Typesync prefers to issue an error rather than make assumptions and try to make the most of the situation.

Our philosophy is straightforward: developer tools should consistently perform as expected, without any surprising behavior—even if such behavior might be seen as beneficial to some. Essentially, tools should remain “dumb” in their operations, even while performing complex tasks.

The predictability that this behavior produces means that you always know what to expect from Typesync as it does only what it promises to do. Nothing more, nothing less.

2. Ejectability

Flexibility is central to Typesync’s design. It’s developed to be non-intrusive, with no lock-in effects. You can easily integrate Typesync into your projects without major changes to your codebase.

Typesync is not a library that tightly attaches itself to your code. You can eject at any time by simply copy-pasting the generated output into your source code without having to refactor your code. Moving away from Typesync is as simple as integrating it.

3. Configurability

Everything that can be reasonably expected to be configurable is made explicitly configurable by Typesync. Whether it’s defining how your schema is structured or customizing the output for different programming languages, Typesync provides you with the flexibility to make those decisions.

Typesync is designed to be as customizable as possible, allowing you to tailor the tool to your project’s requirements.