Open-source project
specta-rs/specta avatar
specta-rs/specta

Specta: exporting Rust types to TypeScript, Swift and ten other languages

Easily export your Rust types to other languages

649 stars73 forksRustMIT

At a glance

What is it?
Specta is a Rust library that derives a type description from your structs and enums and exports it as TypeScript, Swift, OpenAPI, Zod, Go, Kotlin, Python, Java, C#, JSON Schema, Valibot or Rust source. Two of those twelve exporters are stable and the rest are explicitly partial, which is the first thing to check before choosing it.
Who is it for?
Specta fits a Rust team serving a TypeScript or Swift front end that wants the client types generated from the server types instead of maintained twice, and that has settled on which exporter it needs. It does not fit a team that needs a partial exporter to be complete today, since the README marks ten of the twelve as partial with complex types still in progress, and it does not fit a project that cannot take a macro dependency in its public API types.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One Rust type, many target languages

Specta is described as a library that makes it easy to export your Rust types to other languages, and the mechanism is a derive. You put #[derive(Type)] on a struct or an enum and the macro records the shape; an exporter then turns that record into code in the target language. The repository is a Cargo workspace with one member per exporter, specta-typescript, specta-swift, specta-rust, specta-openapi, specta-go, specta-kotlin, specta-python, specta-jsonschema, specta-zod, specta-valibot, specta-csharp and specta-java, alongside the core specta/ and the specta-macros/ and specta-serde/ crates. The licence is MIT, the default branch is main, and the last push to the repository was on 2026-10-20. The topic list includes tauri-specta and typescript, which tells you the intended centre of gravity: a Rust server with a typed web or desktop front end.

The derive plus a registry is the whole API

The example in the README shows the shape of the workflow. The types are declared with #[derive(Type)], and a generic type parameter is supported, with GenericType<A> carrying a field of type A.

rust
use specta::{Type, Types};
use specta_typescript::Typescript;

#[derive(Type)]
pub struct TypeOne {
    pub a: String,
    pub b: GenericType<i32>,
    #[serde(rename = "cccccc")]
    pub c: MyEnum,
}

A Types registry collects the roots of your type graph, and the exporter is given that registry.

rust
fn main() {
    let types = Types::default()
        // You don't need to specify `GenericType` or `MyEnum` because they are referenced by `TypeOne`
        .register::<TypeOne>();

    Typescript::default()
        .export_to("./bindings.ts", &types)
        .unwrap();

Only the roots are registered, and the comment in the example explains why: the referenced types come along automatically. That is the part that makes this usable at scale, because a type graph of two hundred structs does not need two hundred registrations. The serde attribute on the field is honoured too, and the expected TypeScript output in the README shows the renamed field cccccc, so the wire format stays the single source of truth rather than being duplicated in the client. The export function returns a string and the export_to function writes it to a path, with the difference shown by an assertion in the example that compares the generated file contents against an expected literal.

Installing the core crate and the one exporter you need

The README tells you to add the specta crate along with any Specta language exporter crate, and the install block lists each exporter with its status in a comment.

bash
# Core Specta library
cargo add specta

# Language exporters (choose one or more)
cargo add specta_typescript  # TypeScript (stable)
cargo add specta_swift       # Swift (stable)
cargo add specta_rust        # Rust (partial)

The pattern continues for the rest: specta_openapi for OpenAPI 3.0 schemas in JSON or YAML, specta_java for Java 17, specta_go for Go, specta-python for Python 3.13 type hints, specta_kotlin, specta_jsonschema, specta_zod, specta-valibot and specta-csharp, each marked partial. The exporter crates live separately on crates.io, so you take on the dependencies only for the languages you actually target. The workspace Cargo.toml also shows the linting posture, with missing_docs and unsafe_code at warn level for the workspace and a clippy configuration that bans unwrap_used, panic and todo in the library code, with a comment explaining that too_many_arguments and type_complexity are allowed because they are internal implementation details.

Only TypeScript and Swift are called stable

The language support table in the README is the most important part of the documentation, and it does not flatter the project. TypeScript is marked stable with full type support, generics and unions. Swift is marked stable with idiomatic Swift, custom Codable and Duration support. Everything else is marked partial: Rust, OpenAPI, Go, Kotlin, JSON Schema, Zod, Valibot, Python, C# and Java. The legend defines partial as basic functionality implemented with complex types in progress, and stable as production ready with comprehensive test coverage. The Implementation Status section repeats the split as two production ready exporters, ten partially implemented ones covering Rust, OpenAPI, Go, C#, Java, Kotlin, JSON Schema, Zod, Valibot and Python, and zero planned.

Each exporter also has its own documented feature list, and those lists are where the partial status gets concrete. The Rust exporter is noted as producing Rust source, modules and generics with structural wire-only shapes erroring. The OpenAPI exporter covers OpenAPI 3.0 schemas with generics, JSON and YAML. Go gets structs, generics, string enums, layouts and Serde. Kotlin gets data and sealed classes, generics and optional Kotlinx. Zod 4 and Valibot get schemas, inferred types, generics and layouts. Python gets Python 3.13 TypedDicts and type hints, and C# and Java get records, enums, generics and file layouts, with Java 17 records, enums and sealed interfaces. The README also says that for the most up to date status of each exporter you should check the individual crate documentation and issue trackers.

The ecosystem around it, for RPC and desktop apps

Specta can be used in your application either directly or through a library which simplifies the process, and three of those libraries are named. rspc is for easily building end-to-end typesafe APIs, tauri-specta is for typesafe Tauri commands and events, and TauRPC is a Tauri extension that gives you a fully-typed IPC layer. The features list in the README also mentions getting function types, so that libraries like tauri-specta can use them, and that Specta supports a wide range of common crates in the Rust ecosystem, and that it supports type inference, meaning it can determine the type of a fn demo() -> impl Type. That last point matters for anyone who wants to expose handlers, since a function returning an opaque type is still a type Specta can describe.

The topic list carries the same signal from the other side, with rspc, tauri-specta, typesafe and typescript among the tags. For a Rust backend serving a web front end, the typical path is to derive the types, export them to TypeScript, and let the front end import the generated file, and the RPC libraries exist so that the same types can drive the call signatures rather than only the data shapes.

Where Specta is the wrong choice

Three cases point elsewhere. The first is a project that needs a partial exporter to be finished today. If your target is Go or Kotlin and your model uses the complex types those exporters are still working on, the exported file will not compile, and the workaround is to hand-maintain the client types, which is the duplication you were trying to remove. The second is a codebase that cannot take a macro in its public API types. #[derive(Type)] is a proc macro, and it sits in the type definition, so every consumer of that type needs the Specta crate in scope, and the workspace even has to allow unsafe_code for the ctor feature, which the lint comment says is an optional feature.

The third is a project that already has its contract in a schema. If you are generating TypeScript from an OpenAPI document, generating OpenAPI from TypeScript, or keeping a Zod schema as the source of truth, Specta is adding a second source rather than removing one. The exporters for JSON Schema, OpenAPI and Zod are genuinely useful when Rust is the origin of the types, but in a schema-first project they are a round trip you do not need.

A last practical point is the release state. The recent tags are v2.0.0-rc.25, v2.0.0-rc.24 and v2.0.0-rc.23, all release candidates for a 2.0 line, so the crate you pull is a release candidate for a major version rather than a settled one, and the README itself says the patterns and status are subject to change.

Editorial conclusion

Specta fits a Rust team serving a TypeScript or Swift front end that wants the client types generated from the server types instead of maintained twice, and that has settled on which exporter it needs. It does not fit a team that needs a partial exporter to be complete today, since the README marks ten of the twelve as partial with complex types still in progress, and it does not fit a project that cannot take a macro dependency in its public API types. Before you adopt it, check the status table in the README for the specific exporter you need, since only TypeScript and Swift are listed as stable, and read the exporter's own crate documentation, which the README says is the most up-to-date source for each one's status.

Frequently asked questions

What does Specta do in Rust?

It exports your Rust types to other languages. You derive the type description on structs and enums with #[derive(Type)], register the root types in a Types registry, and an exporter generates the target language, for example a TypeScript file through specta-typescript. It also supports type inference, so it can determine the type of a function returning impl Type.

Which Specta exporters are stable?

TypeScript and Swift are marked stable, described as production ready with comprehensive test coverage. The other ten, covering Rust, OpenAPI, Go, C#, Java, Kotlin, JSON Schema, Zod, Valibot and Python, are marked partial, meaning basic functionality is implemented and complex types are still in progress. The README recommends checking each exporter's own crate documentation for the current status.

How do I add Specta to a Rust project?

Add the core crate with cargo add specta, then add the exporter crate for the language you need, for example cargo add specta_typescript or cargo add specta_swift. The exporter crates are published separately, so you only take on the dependencies for the languages you actually target.

Do I have to register every type with Specta?

No. The README's example registers only the root type with Types::default().register::<TypeOne>(), and the comment explains that you do not need to specify GenericType or MyEnum because they are referenced by TypeOne. The referenced types are collected automatically.

How does Specta handle serde attributes on exported fields?

The derive respects them. In the README's TypeScript example the field c is annotated with #[serde(rename = "cccccc")], and the expected generated output shows that field as cccccc, so the wire format naming carries over to the exported type rather than being restated in the client.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. specta-rs/specta on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/specta-rs-specta.svg)](https://hysenlabs.com/projects/specta-rs-specta)

Was this useful?