Write Effect programs in TypeScript syntax, with typed errors and services the compiler checks.
Get started · Cheat sheet · Contribute
Warning
VibeLang is v0. The language will change in major, breaking ways. It is not suitable for production use.
Compatibility. Use exactly
effect@4.1.0; keep@effect/platform-nodeand@effect/vitestat4.1.0when used.Existing projects need an
@effect/platform-node-sharedoverride. The starter includes the pins and override.Use
vibelangto check.vibesource. Stocktscandvue-tscsee only thex.d.vibe.tsdeclarations the compiler writes, which resolve.vibeimports whenallowArbitraryExtensionsis on.For mixed editing in VS Code, let the VibeLang server own TypeScript files: disable the built-in TypeScript and JavaScript Language Features extension for this workspace.
Effect gives you typed failures, dependency injection, concurrency and resource management. VibeLang gives those programs direct syntax: an effect function returns an Effect, try runs one, and its errors and required services stay in its type. Every form is exact sugar for the Effect TypeScript it replaces.
Plain and async functions are TypeScript's, so your .ts code and npm packages call as they do in TypeScript. The compiler tracks what a function may throw, and Effect code calls one that may throw through Effect's own Effect.try (TypeScript boundaries):
// pending: VIP-1, VIP-4, VIP-5, VIP-6, VIP-13
import { parseSettings } from "./settings.ts"
error BadSettings { cause: unknown }
effect function settings(text: string) {
return try Effect.try({ try: () => parseSettings(text), catch: (cause) => new BadSettings({ cause }) })
}
Use Effect services for platform, clock and randomness APIs, such as Console, Clock, Random and FileSystem. A .vibe file reaches Node.js and web APIs only through Effect's services, provided by a platform layer (platform services).
Here is one program in both languages: a typed failure, an injected service, two concurrent lookups and a fallback.
VibeLang
// pending: VIP-1, VIP-5, VIP-6, VIP-9, VIP-12, VIP-13, VIP-18, VIP-26
error UserNotFound { id: string }
service Users {
find(id: string): string!UserNotFound
}
const UsersLive = Layer.succeed(Users, {
effect find(id: string) {
if (id !== "42") throw new UserNotFound({ id })
return "Ada"
}
})
export effect function main() {
provide UsersLive {
const users = try Users
const [name, fallback] = try.all [
users.find("42"),
effect { return users.find("7") catch () => "Guest" }
]
try Console.log(name, fallback)
}
}
runPromise(main())
Effect 4 TypeScript
import { Console, Context, Data, Effect, Layer } from "effect"
class UserNotFound extends Data.TaggedError("UserNotFound")<{ id: string }> {}
class Users extends Context.Service<Users, {
find: (id: string) => Effect.Effect<string, UserNotFound>
}>()("Users") {}
const UsersLive = Layer.succeed(Users, {
find: (id: string) => id === "42"
? Effect.succeed("Ada")
: Effect.fail(new UserNotFound({ id }))
})
const main = Effect.gen(function* () {
const users = yield* Users
const [name, fallback] = yield* Effect.all([
users.find("42"),
users.find("7").pipe(Effect.catch(() => Effect.succeed("Guest")))
], { concurrency: "unbounded" })
yield* Console.log(name, fallback)
}).pipe(Effect.provide(UsersLive))
Effect.runPromise(main)6.6% fewer tokens across 47 cheat-sheet pairs; VibeLang is longer in 20 pairs. The example above uses 136 vs 202 tokens (32.7% fewer).
Counts use o200k_base. Both print Ada Guest. The counts cover the complete source files, including TypeScript's imports and runner, which VibeLang supplies. It is one example, not a general ratio. Reproduce the counts and both runs. Token counts measures the skill agents load, the docs and every cheat-sheet pair.
service declares a dependency and provide supplies it. try runs an Effect, try.all runs Effects concurrently, and catch handles the declared failure. Console and Layer are in scope without imports, and the module runs main with a runner, as Effect TypeScript does. A contract reads Result!Error!Services, exactly Effect.Effect<Result, Error, Services>; leave it out and the compiler infers it.
match is TC39 pattern matching with exhaustiveness checks and generated error matchers: leave a variant of a data type out of a match without default, and the compiler reports the missing case.
The compiler checks .vibe and .ts files together and emits JavaScript. Set "declaration": true in compilerOptions to emit .d.ts files for TypeScript consumers. The starter leaves declaration output off.
npm create vibelang@latest my-app
cd my-app
npm run start
npm run testWith Bun as the package manager (optional; the app runs on Node):
bun create vibelang@latest my-app
cd my-app
bun run start
bun run testThe starter's start script runs Node and uses @effect/platform-node, even when Bun installs its dependencies. To use Bun as the runtime, see Run sources with Bun.
You need Node 22.22.3 (22.x) or Node 24.11.1 or later. If you use Bun, use Bun 1.4 or later. Bun is optional. npm run start prints:
Hello, VibeLang!
Recovered from EmptyName
Replace src/main.vibe with this program and run npm run start again:
// pending: VIP-1, VIP-4, VIP-5, VIP-13, VIP-26
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
error EmptyName
effect function greet(name: string): string!EmptyName {
if (name === "") throw new EmptyName()
return `Hello, ${name}!`
}
export effect function main() {
try Console.log(try greet("VibeLang"))
const recovered = greet("") catch () => "Hello, stranger!"
try Console.log(recovered)
}
NodeRuntime.runMain(main())
Hello, VibeLang!
Hello, stranger!
To add VibeLang to an existing project, see Installation.
The vibelang package ships a VS Code and Cursor extension. From your project, after installing dependencies:
code --install-extension node_modules/vibelang/editor/vibelang.vsixUse cursor instead of code for Cursor. The extension isn't on the marketplaces yet. It runs the project's compiler as a language server for diagnostics, hovers and completions. Editor setup covers Neovim, Zed and other LSP clients.
- Guides and reference
- Syntax cheat sheet: every form beside the Effect code it replaces
- VibeLang for Effect users
- Specification
- Examples: services, an HTTP API, retries and races, streams, React, Vue and Svelte
Report a bug with a minimal reproduction, or propose an idea, in Issues. A maintainer accepts issues for discussion, labels each with an expected timeline and commitment, and links a proof of concept as a draft pull request titled poc: #<n> that you can check out and run. CONTRIBUTING.md explains the process, and MAINTAINERS.md covers triage and releases. Report vulnerabilities privately through SECURITY.md.
MIT, except the TypeScript fork in typescript/ and the compiler packages built from it, vibelang and vibelang-<platform>, which use the Apache License 2.0 and keep its notices.
2,296 tokens (o200k_base, js-tiktoken 1.0.21), counted by the docs flow.