Skip to content

Formatting Guide

This document specifies the standard formatting rules for the VDL IDL. Consistent formatting enhances code readability, maintainability, and collaboration.

Note: All style conventions are automatically enforced by the official VDL formatter. Run it manually with vdl fmt ./schema.vdl, or let the built-in LSP formatter (bundled with the VS Code extension) format files on save.

  • Encoding: UTF-8.
  • Line Endings: Use newline characters (\n).
  • Trailing Whitespace: None.
  • Final Newline: End non-empty files with one newline.
  • Use 2 spaces per indentation level.
  • Do not use tabs.
type Example {
field: string
}

Top-level elements include include, type, enum, const, pattern, rpc, and standalone docstrings or comments.

  • Includes: Group consecutive include statements together without blank lines between them.
  • Separation: Separate each top-level element with one blank line.
  • Preservation: Intentionally placed blank lines in the source are respected.
include "./common.vdl"
include "./auth.vdl"
const MAX_PAGE_SIZE = 100
type Order {
id: string
total: float
}
rpc Orders {
proc GetOrder { ... }
}
  • Opening braces ({) are on the same line as the declaration, preceded by one space.
  • Contents inside non-empty blocks start on a new, indented line.
  • The closing brace (}) is placed on its own line, aligned with the declaration.
type User {
id: string
name: string
}

Procedures (proc) and streams (stream) must be defined inside an rpc block. Separate each endpoint with one blank line.

rpc Service {
proc Get {
input {
id: string
}
output {
data: string
}
}
stream Watch {
input {
filter: string
}
output {
event: string
}
}
}
  • Each field is placed on its own line.
  • Fields without docstrings can be placed consecutively without blank lines.
  • When a field has a docstring, separate it from the preceding field with one blank line.
  • The spread operator (...) should be placed at the beginning of the block.
type User {
...AuditMetadata
""" The user's display name. """
name: string
""" The user's email address. """
email: string
age?: int
}

In procedures and streams, separate the input and output blocks with one blank line.

proc CreateUser {
input {
name: string
email: string
}
output {
userId: string
createdAt: datetime
}
}
ContextRuleExample
Colons (:)No space before, one space afterfield: string
Braces ({)One space before in declarationstype User {
Brackets ([])No spaces for array typesstring[]
Optional marker (?)Immediately follows the field nameemail?: string
Map syntaxNo extra spacesmap<User>

Comment content is preserved exactly, including internal whitespace.

  • Standalone Comments: Use // or /* ... */ on their own lines, indented to the current block level.
  • End-of-Line Comments: Place after code on the same line, with at least one space separating them.
// This is a standalone comment
type Example {
field: string // End-of-line comment
}
  • Place docstrings immediately above the element they document.
  • Enclose in triple quotes ("""), preserving internal newlines and formatting.
  • For fields, prefer concise, single-line docstrings.
"""
Represents a user in the system.
"""
type User {
""" The unique identifier. """
id: string
""" The user's full name. """
name: string
}

The deprecated keyword marks elements as deprecated. Place it on its own line immediately before the element definition. If a docstring exists, place deprecated between the docstring and the element.

deprecated type LegacyUser {
// ...
}
"""
Documentation for this service.
"""
deprecated rpc OldService {
deprecated proc FetchData {
// ...
}
}
deprecated("Use NewType instead")
type OldType {
// ...
}

The formatter automatically enforces the following naming conventions:

ElementConventionExample
TypesPascalCaseUserProfile
EnumsPascalCaseOrderStatus
Enum MembersPascalCasePending, InProgress
RPC ServicesPascalCaseUserService
ProceduresPascalCaseGetUser
StreamsPascalCaseNewMessages
PatternsPascalCaseUserEventSubject
FieldscamelCaseuserId, createdAt
ConstantsUPPER_SNAKE_CASEMAX_PAGE_SIZE

Acronyms longer than two letters are treated as regular words:

// Correct
type HttpRequest { ... }
type JsonParser { ... }
// Incorrect
type HTTPRequest { ... }
type JSONParser { ... }