TypeSpec Documentation
website·Indexed 35 days ago
https://typespec.io/docsDocumentation for TypeSpec, a language for defining API schemas. It covers language basics including models, operations, decorators, and templates, as well as guides for REST APIs, OpenAPI v2 and v3 mapping, and support for JSON Schema, Protobuf, and Events.
What's inside TypeSpec
- Visibility is a TypeSpec language feature used to share a single model across multiple operations while controlling which properties are visible in specific contexts. It allows the definition of different "views" of a model. Visibility applies only to model properties and is managed via visibility classes (enums). Emitters use these flags to include or exclude properties in the generated output.
Understand Http.File semantics
The
@typespec/httplibrary provides a special model,TypeSpec.Http.File, used to represent files. Unlike ordinary models that would be interpreted as structured JSON,Http.Filesignals that an operation has a binary payload with an arbitrary content-type.Http.Filecontains three special properties:contents: The actual binary body of the request, response, or multipart payload. This location cannot be changed by subtypes.contentType: (Optional) The MIME type sent in theContent-Typeheader. This location cannot be changed by subtypes.filename: (Optional) The name of the file sent in thefilenameparameter of theContent-Dispositionheader. This is valid for responses and multipart payloads, but not requests. This location can be overridden by subtypes using HTTP metadata.
Use decorators to attach metadata to TypeSpec types
Decorators in TypeSpec allow developers to attach metadata to types or compute types based on inputs. Most declarations that create a Type can be decorated, including namespaces, interfaces, operations (and their parameters), scalars, and models (and their members).
Constraints:
- Aliases cannot be decorated because they do not create new Types.
- Type expressions (such as unions using
|) and anonymous models cannot be decorated as they are not declarations.
Define OpenAPI v3 Request Bodies in TypeSpec
In TypeSpec, an OpenAPI v3 Request Body object is represented as an operation parameter decorated with
@body.- Description: Use the
@docdecorator. - Required/Optional: A
?following the parameter name indicatesrequired: false; otherwise, it isrequired: true. - Content Type: Specified using a
@header contentTypeparameter. If multiple values are provided in a union, the resulting OpenAPIcontentobject will have one entry for each value. - Multiple Schemas: To define multiple
contententries with different schemas for the same path and method, use the@sharedRoutedecorator to define separate operations.
// Single content type @put op uploadImage(@header contentType: "image/png", @body image: bytes): void; // Multiple content types (union) @post op analyze( @header contentType: "application/octet-stream" | "application/pdf" | "image/jpeg", @body image: bytes, ): string | Error; // Multiple content entries with different schemas using @sharedRoute @route(":process") namespace Process { @sharedRoute @post op process(...Widget): Widget | Error; model CsvBody { @header contentType: "text/csv"; @body _: string; } @sharedRoute @post op process2(...CsvBody): Widget | Error; }- Description: Use the
Add new operations to a specific API version
Use the@addeddecorator to introduce a new operation starting from a specific version. The TypeSpec definition should reflect the current state of the API, while the decorator marks the version where the operation first became available.enum Versions { v1, v2, } @route("/widget") op list(): Widget[] | Error; @added(Versions.v2) @route("/widget/{id}") op get(...Resource.KeysOf<Widget>): Widget | Error;Apply hierarchical routing to namespaces and interfaces
The@routedecorator can be applied to a parent namespace or interface. All nested operations, interfaces, and namespaces will automatically be prefixed with the parent's route, allowing for modular API path definitions.@route("/store") namespace PetStore { op hello(): void; // Resulting route: `/store` @route("ping") op ping(): void; // Resulting route: `/store/ping` @route("/pets") interface Pets { list(): Pet[]; // Resulting route: `/store/pets` @route("{petId}") read(petId: string): Pet; // Resulting route: `/store/pets/{petId}` } }Create custom File models
Custom file types can be created by providing arguments to the
Http.Filetemplate or extending it. This is used to restrict allowed media types (Content-Type) or define textual files.Note: You can override existing properties of
Http.Filevia extension, but you cannot define additional properties.// Narrowing allowed media types using an alias alias ImageFile = File<"image/png" | "image/jpeg">; // Narrowing using a model extension model ImageFile extends File<"image/png" | "image/jpeg"> {} // Alternative extension by overriding the contentType property model ImageFile extends File { contentType: "image/png" | "image/jpeg"; }Create scalar values using constructors
Scalars can be declared withinitconstructors to record how a specific value should be created. These constructors do not contain runtime code; they serve as metadata for emitters to generate the correct value in the target language.scalar ipv4 extends string { init fromInt(value: uint32); } const homeIp = ipv4.fromInt(2130706433);Reference enum members and union variants as values
References to enum members and union variants behave like scalar literals: they are treated as types in a type context and as values in a value or ambiguous context. Note: It is an error to refer to a union variant as a value if its associated type is not a literal type.enum Color { red, green, blue } @setColorValue(Color.red) // treated as value "red" union ColorUnion { red: "red", other: string, } @setColorValue(ColorUnion.red) // ok: passes scalar "red" @setColorValue(ColorUnion.other) // error: 'other' is a type (string), not a literal valueDefine path and query parameters in TypeSpec operations
Use the@pathand@querydecorators to specify how operation parameters should be passed in an HTTP request. Parameters marked with@queryare passed as query string parameters, while parameters marked with@pathare passed as part of the URL path.@route("/pets") namespace Pets { op list(@query skip: int32, @query top: int32): Pet[]; op read(@path petId: int32): Pet; }Manage authentication overrides in the application hierarchy
The@useAuthdecorator follows a hierarchical override pattern. A decorator applied to a child type (e.g., an operation) overrides the security scheme of its parent (e.g., a namespace). To extend rather than replace the parent's security, you must explicitly re-specify the parent's schemes in the child's decorator.@useAuth(BasicAuth) namespace MyService { // Overrides BasicAuth: only ApiKey is used @useAuth(ApiKeyAuth<ApiKeyLocation.query, "api_key"> op one(): void; // Inherits BasicAuth op two(): void; // Extends BasicAuth: accepts either Basic OR ApiKey @useAuth(BasicAuth | ApiKeyAuth<ApiKeyLocation.query, "api_key"> op three(): void; }Handle additional properties using Record<T>
The
Record<T>model allows a model to have an arbitrary number of properties of type T. This can be combined with named models in three ways:- Spread (
...): Includes explicitly defined properties plus any additional properties of type T. Explicit properties can have types incompatible with T. - Is (
is): All properties in the model, including explicitly defined ones, must be of type T. - Extends (
extends): Similar semantics tois, but establishes a formal relationship between models.is Record<T>is generally recommended overextends.
// Using spread: age can be int32 while others are strings model Person { age: int32; ...Record<string>; } // Using is: all properties must be strings model PersonIsRecord is Record<string> { name: string; }- Spread (