Skip to the content.

Conduit

CI crates.io crates.io downloads npm version npm downloads License

Conduit is a reverse proxy, API gateway and static file server written in Rust, built on Cloudflare Pingora. You describe your sites, routes, authentication, limits and caching in one YAML or JSON file and run one executable. Conduit terminates TLS, balances and protects your upstreams, serves a single-page app next to its API, proxies raw TCP, lets you add your own logic with scripts and plugins, and picks up configuration changes without dropping connections. It runs from a config file, from a Docker image, or on Kubernetes, where every ConduitSite resource becomes a site and changes apply on their own.

Clients reach Conduit over HTTP/1.1, HTTP/2, WebSocket or TCP. Inside Conduit, listeners feed an ordered chain of guards and a router that picks a handler: reverse proxy, TCP proxy, static files or upload. Handlers use your services and files on disk. Configuration comes from a YAML or JSON file or from Kubernetes ConduitSite resources. Redis and an identity provider are optional.

npx @lopatnov/conduit init   # write a starter conduit.yaml
npx @lopatnov/conduit        # run it

Other ways to run it: npm install -g @lopatnov/conduit · pre-built binaries for Linux, macOS and Windows · the Docker image ghcr.io/lopatnov/conduit · cargo install lopatnov-conduit · Kubernetes with ConduitSite resources. Details: Installation.

Table of Contents

Quick start

conduit init          # interactive wizard
conduit init --yes    # non-interactive: accept the defaults and write conduit.yaml
conduit validate      # check the config
conduit               # start

A minimal hand-written config. YAML and JSON are equivalent:

# conduit.yaml
port: 8080
static: ./dist
proxy:
  /api: http://localhost:4000

The three-line config serves ./dist for GET / and GET /logo.png and proxies GET /api/users to http://localhost:4000/api/users.

curl http://localhost:8080/__health__
# {"status":"ok"}

→ Ready-to-run configs: examples/ · → Every field: docs/configuration.md · → Other ways to install: Installation

What Conduit does

Sixteen feature groups: routing; proxy and balancing; resilience; caching; static files; TCP and uploads; response shaping; TLS; authentication; traffic control; scripting; observability; operations; developer tools; packaging; Kubernetes. Items marked with an asterisk need an optional build feature.

Where each group is documented in detail:

Group Read
Routing, redirects, path rewrite Routes · Redirects · URL rewriting
Proxy and balancing Proxy · Load balancing · Sticky sessions · Mirroring · Upstream TLS · Connection pool
Resilience Health checks · Circuit breaker · Retry · Outlier detection
Caching Proxy cache
Static files Static files · Fallback
TCP and uploads TCP proxy · Upload
Response shaping Compression · Security headers · CORS · Error masking · Response transform
TLS TLS / HTTPS · mTLS · HTTP/2
Authentication Basic · API key · JWT · Forward auth · Consumers
Traffic control Rate limiting · Limits · Priority routing · IP filter
Scripting Rhai guide · WebAssembly guide
Observability Logging · Metrics · OpenTelemetry · Response time · Server-Timing
Operations Hot reload · Admin API
Developer tools CLI · JSON Schema for editors
Packaging and Kubernetes Deployment guide · ConduitSite resources · Building

An asterisk on the map means the feature is optional at build time. Choose your build shows which build has what.

How a request flows

A request passes through seven stages in order: accept, gate, protect, auth, shape, serve and respond. A guard that rejects the request answers it immediately with 403, 400, 429, 503 or 401. After routing, the proxy checks per-route limits and the circuit breaker, picks an upstream, rewrites the request and handles the response.

Guards run in a fixed order, so you can reason about what happens first: a client blocked by the IP filter never reaches rate limiting or auth, and a request that fails auth never reaches your scripts or your upstream. Routing then picks the first matching route, and a handler serves it. /__health__ is answered at the end of the gate stage, before the host check, limits and auth.

Features by example

Each block is a complete config you can save as conduit.yaml and check with conduit validate. Features marked (needs x) are optional at build time; see Choose your build.

Reverse proxy and load balancing

A balancer picks an upstream with one of eight strategies. Unhealthy and ejected upstreams are skipped, upstreams at their connection cap are skipped, and a failed attempt is retried on another target within a retry budget.

port: 8080
proxy:
  /api:
    targets:
      - http://api-1:4000
      - http://api-2:4000
      - http://api-3:4000
    strategy: least-conn               # also round-robin, p2c, ip-hash, consistent-hash …
    healthCheck: { path: /health, intervalSecs: 10 }
    maxConnectionsPerUpstream: 200     # 503 instead of queueing when every target is full
    retry: { attempts: 2, conditions: [connection_error, 5xx] }
outlierDetection: { consecutive5xx: 5 }   # eject a target that keeps failing real requests

→ Details: Load balancing · Health checks · Circuit breaker · Retry

More proxy options

port: 8080
proxy:
  /api:
    targets: [https://api-1.internal:8443, https://api-2.internal:8443]
    backup: https://api-standby.internal:8443    # used while every target is unhealthy
    upstreamTls: { verify: true }                # on by default; checks the certificate against the host in the target URL
    http2: true                                  # HTTP/2 to the upstream
    mirror: http://api-v2:4000                   # shadow copy of each request (headers only); the reply is dropped
    websocket: true                              # allow WebSocket upgrades on this route

The mirror receives a copy of the request headers, Authorization and cookies included, so mirror only to a service you trust. Upstream certificates are always checked against the host in the target URL, so write the name the certificate carries; upstreamTls.serverName does not change that yet (#583). → Details: Backup targets · Upstream TLS · Mirroring · Connection pool

Static files and single-page apps

port: 8080
static: ./dist
fallback: { file: ./dist/index.html, status: 200 }   # unknown paths get the app shell
compression: true
proxy:
  /api: http://localhost:4000

→ Details: Static files · Fallback · Compression

Headers, compression and error masking

port: 8080
static: ./dist
compression: true                  # br, zstd or gzip: static files, fallback pages and metrics
securityHeaders: true              # nosniff, frame, referrer and XSS headers; HSTS is opt-in
cors: { origins: ["https://app.example.com"] }
maskErrors: true                   # 5xx bodies from upstreams become a generic JSON error
responseTransform:
  setHeaders: { X-Served-By: conduit }
  removeHeaders: [Server]
proxy:
  /api: http://localhost:4000

→ Details: Compression · Security headers · CORS · Error masking · Response transform

HTTPS with automatic certificates (needs acme)

host: example.com
port: 443
http2: {}
tls:
  acme: { email: admin@example.com }   # Let's Encrypt; it validates over port 80
  httpRedirectPort: 80                 # plain HTTP is redirected to HTTPS
proxy: http://localhost:4000

Bring your own certificate instead with tls: { cert: ./fullchain.pem, key: ./privkey.pem }. Conduit renews ACME certificates in the background and writes them to disk, but a running process keeps serving the certificate it loaded at start, so restart it within 30 days of expiry (any deploy does it). → Details: TLS / HTTPS · mTLS

Authentication

Four layers: network (IP filter, TLS and mTLS, allowed hosts), abuse control (request limits, rate limits, load shedding), identity (consumers, Basic auth, API keys, JWT, forward auth) and hardening (security headers, CORS, header hygiene, error masking).

port: 8080
jwtAuth:                               # needs jwt
  jwksUrl: https://auth.example.com/.well-known/jwks.json
  audience: [my-api]
requestTransform:
  setHeaders:
    X-User-ID: ""         # pass a claim on to your service
proxy:
  /api: http://localhost:4000

Prefer something simpler? basicAuth: { users: { alice: "$ALICE_PASSWORD" } } and apiKey: { keys: ["$API_KEY"], header: X-API-Key } work the same way, and forwardAuth hands the decision to your own service. → Details: Basic · API key · JWT · Forward auth · Consumers

Rate limiting

port: 8080
rateLimit: { windowSecs: 60, limit: 300 }          # per client IP, for the whole site
proxy:
  /api/payments:
    targets: [http://payments:4000]
    rateLimit: { windowSecs: 60, limit: 10 }       # a stricter limit for this route, also per client IP

Limits are counted per client IP unless you set keyBy: "header:X-Name". A header is chosen by the client and can be forged, so only key on one that a trusted proxy in front of Conduit sets. For quotas per authenticated client use consumers. Add store: "redis://host:6379" to share counters between instances (needs redis). → Details: Rate limiting

Caching (needs cache)

A request is looked up by host, scheme, path and query. A fresh hit is served from the store, a stale entry is served while one background fetch refreshes it, and a miss goes to the upstream and is stored.

port: 8080
proxy:
  /api:
    targets: [http://backend:4000]
    cache:
      store: memory                    # or redis://… or disk:/path
      ttlSecs: 60
      staleWhileRevalidateSecs: 300    # serve a stale copy while refreshing in the background
      staleIfErrorSecs: 600            # and when the upstream is down
      skipIfCookie: true               # never cache requests that carry a cookie

→ Details: Proxy cache

Your own logic: Rhai scripts and WebAssembly plugins

Scripts and plugins run after the built-in guards and again on the response. Rhai needs no build step; WebAssembly plugins can be written in Rust, C, Go or AssemblyScript and can also read the client IP and change the headers sent upstream. Both fail open.

port: 8080
middleware:
  - type: script                       # Rhai, needs rhai
    path: ./scripts/auth-check.rhai
  - type: wasm                         # WebAssembly, needs wasm
    path: ./plugins/my-plugin.wasm
proxy:
  /api: http://localhost:4000
// scripts/auth-check.rhai: deny requests without an Authorization header
let token = request.header("Authorization");
if token == "" {
    response.status = 401;
    response.body   = "Unauthorized";
    return false;
}
true

Scripts and plugins fail open, so keep your real access checks in the auth guards. → Guides: Rhai · WebAssembly

Observability

sites:
  - port: 8080
    logging: json                      # structured access log; every request gets an X-Request-ID
    metrics: { path: /__metrics__, token: "$METRICS_TOKEN" }   # Prometheus
    responseTime: true                 # adds an X-Response-Time header (milliseconds)
    serverTiming: true                 # Server-Timing: total and upstream, for browser DevTools
    proxy: http://localhost:4000
global:
  otlp: { endpoint: "http://tempo:4317", serviceName: my-api, sampleRate: 0.1 }   # needs otlp

→ Details: Logging · Metrics · OpenTelemetry · Response time · Server-Timing

Several sites in one process

sites:
  - host: app.example.com
    port: 443
    tls: { acme: { email: admin@example.com } }
    static: ./dist
    proxy: { /api: http://api:4000 }

  - host: admin.example.com
    port: 443
    tls: { acme: { email: admin@example.com } }
    basicAuth: { users: { admin: "$ADMIN_PASSWORD" } }
    proxy: http://admin-backend:5000

Kubernetes: one ConduitSite per site (needs kubernetes)

You apply a ConduitSite resource with kubectl. The Kubernetes API stores it next to the other ConduitSite resources. Conduit watches the namespace, builds one configuration from every resource, checks it and swaps it in without a restart; an invalid update is rejected. Requests go to the services each site names.

Instead of a config file, Conduit can read its sites from Kubernetes custom resources. Each ConduitSite is one virtual site, and its spec takes the same fields as a site in conduit.yaml.

# site.yaml
apiVersion: conduit.io/v1
kind: ConduitSite
metadata:
  name: my-app
  namespace: default
spec:
  port: 8080
  host: app.example.com
  proxy:
    /api:
      targets: [http://my-svc:4000]
      healthCheck: { path: /health }
  rateLimit: { windowSecs: 60, limit: 500 }
kubectl apply -f contrib/k8s/conduitsite-crd.yaml    # once per cluster: the ConduitSite definition
kubectl apply -f site.yaml                           # add or change a site
conduit --kubernetes-namespace default               # in the Conduit pod; '*' watches all namespaces

Conduit watches the namespace, combines the resources into one config, checks it and swaps it in without a restart; an update that fails validation is rejected and the running config stays. Use the :latest-full image (or a build with --features kubernetes) and give the pod a service account that may get, list and watch conduitsites. Write access to ConduitSite objects is deploy access: spec takes any site field, file paths such as static and upload.dir included, so grant it only to people who may deploy to the Conduit pod. → CRD, RBAC and Deployment manifests: docs/deployment.md

Raw TCP (needs tcp) and file upload (needs upload)

sites:
  - port: 3306                          # raw passthrough with no auth layer: restrict who can reach this port
    tcp:
      targets: ["mysql-primary:3306", "mysql-replica:3306"]
      strategy: round-robin             # or random

  - port: 8080
    upload:
      path: /upload                     # multipart POST; files get generated names
      dir: ./uploads
      maxFileSizeBytes: 5242880
      allowedMimeTypes: [image/png, application/pdf]

Uploads are open to anyone who can reach the port unless the site also has an auth guard (basicAuth, apiKey or jwtAuth). → Details: TCP proxy · Upload

→ More scenarios: docs/recipes.md covers HTTPS, load balancing, failover, circuit breaker, caching, security hardening, observability and Kubernetes, and examples/ has a validated config for each.

Installation

npx — nothing to install

npx @lopatnov/conduit init      # generate config
npx @lopatnov/conduit           # start the server
npx @lopatnov/conduit validate  # validate config

npm global

npm install -g @lopatnov/conduit
conduit validate
conduit

The npm package downloads the native binary for your platform from GitHub Releases when it is installed (set CONDUIT_SKIP_DOWNLOAD=1 to skip that). Node.js is only needed for the download and for the conduit launcher script; the server itself is the Rust binary. Since 2.0.0 the package installs the full build; platforms: Linux x64 and arm64 (glibc), macOS Intel and Apple Silicon, Windows x64. See the npm package page for details.

Pre-built binaries

Download from GitHub Releases:

Platform Standard Full
Linux x86-64 conduit-x86_64-unknown-linux-gnu.tar.gz conduit-x86_64-unknown-linux-gnu-full.tar.gz
Linux x86-64 musl conduit-x86_64-unknown-linux-musl.tar.gz conduit-x86_64-unknown-linux-musl-full.tar.gz
Linux ARM64 conduit-aarch64-unknown-linux-gnu.tar.gz conduit-aarch64-unknown-linux-gnu-full.tar.gz
Linux RISC-V 64 conduit-riscv64gc-unknown-linux-gnu.tar.gz —
macOS Intel conduit-x86_64-apple-darwin.tar.gz conduit-x86_64-apple-darwin-full.tar.gz
macOS Apple Silicon conduit-aarch64-apple-darwin.tar.gz conduit-aarch64-apple-darwin-full.tar.gz
Windows x86-64 conduit-x86_64-pc-windows-msvc.exe.zip conduit-x86_64-pc-windows-msvc-full.exe.zip
# Linux x86-64, standard
curl -L https://github.com/lopatnov/conduit/releases/latest/download/conduit-x86_64-unknown-linux-gnu.tar.gz \
  | tar xz && ./conduit-x86_64-unknown-linux-gnu --version

Every release also carries SHA256SUMS.txt (it lists every file attached to the release, archives included) and GitHub build-provenance attestations for the bare binaries, which are attached next to the archives (gh attestation verify <binary> --repo lopatnov/conduit; the archives themselves are covered by the checksums only). The musl builds are statically linked; the glibc builds link the system C library dynamically.

cargo install

cargo install lopatnov-conduit                       # default build
cargo install lopatnov-conduit --features standard   # + JWT, consumers, forward auth, cache, ACME
cargo install lopatnov-conduit --features full       # everything

# or pick the features you need
cargo install lopatnov-conduit --features "jwt,cache,rhai"        # default build + JWT auth, caching, Rhai scripts
cargo install lopatnov-conduit --features "tcp,redis,otlp"        # default build + TCP proxy, Redis, OpenTelemetry
cargo install lopatnov-conduit --no-default-features --features proxy          # reverse proxy only
cargo install lopatnov-conduit --no-default-features --features static-server  # static files only, no proxy
cargo install lopatnov-conduit --no-default-features --features gateway        # proxy, auth, cache, ACME; no static files

For build instructions, feature bundles, cross-compilation and troubleshooting see docs/building.md.

Docker

docker pull ghcr.io/lopatnov/conduit:latest        # standard build
docker pull ghcr.io/lopatnov/conduit:latest-full   # full build

docker run -p 8080:8080 \
  -v $(pwd)/conduit.yaml:/etc/conduit/conduit.yaml:ro \
  ghcr.io/lopatnov/conduit:latest -c /etc/conduit/conduit.yaml

The images are FROM scratch (a static musl binary, no shell, no OS userland) and run as UID

  1. On Kubernetes, use the :latest-full image and ConduitSite resources: see the example above. → docker-compose, systemd, Kubernetes and a production checklist: docs/deployment.md

Choose your build

Four build layers, each including the ones below it. Always on: routing, TLS, filters, basic auth, metrics and the Admin API. Default adds reverse proxy, static files, compression and browser live reload. Standard adds JWT, consumers, forward auth, caching and ACME. Full adds scripting, WebAssembly, TCP proxy, upload, Redis, disk cache, fault injection, OpenTelemetry and Kubernetes.

You install with You get
npx / npm install -g full
GitHub Release archives standard, or full (files ending in -full)
Docker :latest is standard, :latest-full is full
cargo install lopatnov-conduit the default build; add --features … for more

If your config uses a feature your binary was built without, conduit validate and the start-up log say so instead of silently ignoring it. To go the other way and find out what a config needs:

$ conduit features -c conduit.yaml
required: proxy
build:    cargo install lopatnov-conduit --no-default-features --features proxy
bundle:   `--features gateway` also covers this configuration

Optional features

Feature What it enables
jwt JWT Bearer-token auth and JWKS URLs (jwtAuth)
consumers Named API clients with per-consumer credentials and rate limits
forward-auth Delegate auth to an external HTTP service (forwardAuth)
rhai Rhai scripting middleware (type: "script")
wasm WebAssembly plugin middleware (type: "wasm") through Wasmtime
tcp Raw TCP proxy mode (type: "tcp" site)
upload Multipart file upload handler (upload: site config)
redis Redis-backed rate limiting and caching
cache Response caching (proxy.*.cache); implies proxy
disk-cache Disk-backed cache store (cache.store: "disk:/path")
acme Automatic certificates from Let’s Encrypt (tls.acme)
fault-injection Fault injection for chaos testing
otlp OpenTelemetry OTLP tracing (global.otlp)
tokio-metrics conduit_eventloop_lag_ms Prometheus gauge (no config key)
kubernetes Kubernetes CRD config provider (--kubernetes-namespace)

Bundles: standard is jwt + consumers + forward-auth + cache + acme on top of the default set; full is every feature above plus standard. static-server and gateway are shorthands for --no-default-features builds: static serving without the proxy, or the proxy, auth, cache and ACME stack without static files. proxy, compression, static and hotreload are on by default.

CLI commands

conduit [-c FILE]                       start the server (default config: conduit.json, conduit.yaml or conduit.yml)
conduit --kubernetes-namespace NS       start from ConduitSite resources instead of a file ('*' = all namespaces; needs kubernetes)
conduit validate [-c FILE]              validate the config — exit 0 = ok, exit 1 = errors
conduit features [-c FILE] [--json]     which Cargo features this config needs
conduit fmt [-c FILE] [--write]         pretty-print / normalise the config
conduit init [--yes] [-o FILE]          setup wizard (--yes = non-interactive)
conduit probe [-c FILE]                 ping every configured upstream and show latency

conduit reload   [--admin ADDR]         hot-reload the config
conduit status   [--admin ADDR]         version, uptime, in-flight requests
conduit status   [--admin ADDR] --upstream   upstream health table (latency, ejected, 5xx)
conduit shutdown [--admin ADDR]         graceful shutdown
conduit upstreams [--admin ADDR]        list upstream health and latency
conduit upstreams add    --route PATH --target URL [--weight N]
conduit upstreams remove --route PATH --target URL
conduit upstreams weight --route PATH --target URL --weight N

conduit completions bash|zsh|fish|power-shell|elvish   shell completion script
conduit man                             generate a man page (roff)

reload, status, shutdown and upstreams talk to the Admin API. Changes made with upstreams add|remove|weight live in memory only and are dropped by the next reload. Full flag reference and exit codes: docs/cli.md

Configuration

Config is a single YAML or JSON file. Without -c, Conduit looks for conduit.json, then conduit.yaml, then conduit.yml in the current directory.

String values can reference environment variables ("$VAR"), which are expanded at start-up and on reload. Keep secrets there rather than in the file.

# conduit.yaml — annotated overview of common fields
port: 443
host: example.com          # virtual host (omit for catch-all)

tls:
  acme:                    # automatic certificates (--features acme)
    email: admin@example.com

http2: {}                  # HTTP/2 settings (on TLS ports it is negotiated via ALPN anyway)
compression: true
securityHeaders: true

proxy:
  /api:
    targets:
      - http://api-1:4000
      - http://api-2:4000
    strategy: least-conn
    healthCheck: { path: /health }
    retry: { attempts: 2, conditions: [5xx, connection_error] }
    cache: { store: memory, ttlSecs: 60 }

rateLimit:
  windowSecs: 60
  limit: 300

jwtAuth:                   # --features jwt
  jwksUrl: https://auth.example.com/.well-known/jwks.json
  audience: [my-api]

logging: json
metrics: { path: /__metrics__, token: "$METRICS_TOKEN" }
healthCheck: true

→ All fields with examples: docs/configuration.md
→ Ready-to-run configs: examples/

Hot reload and the Admin API

Edit the config, check it with conduit validate, apply it with conduit reload. Most settings apply live without dropping connections; the port, certificate files, worker count and admin bind need a restart and reload refuses them. While running, the CLI and the Admin API show status and upstream health, change targets in memory and shut the server down gracefully.

Changing the config does not mean restarting the server: edit the file, run conduit validate, then conduit reload. Most settings apply live, without dropping connections. Settings that need a restart (the port, certificate files, global.workers, global.admin.bind, global.shutdownTimeoutSecs) are refused by reload with a 400 that names the field.

Admin API

An optional management server, off unless you configure global.admin.bind. Bind it to loopback (127.0.0.1:2019, which is also where the CLI commands look by default). Binding it to another interface is possible, but only do that behind a VPN or an SSH tunnel and always with a token.

# conduit.yaml — `global` sits next to `sites:`; in a single-site config it is ignored
global:
  admin:
    bind: "127.0.0.1:2019"
    token: "$ADMIN_TOKEN"    # Bearer token, strongly recommended

sites:
  - port: 8080
    proxy: http://localhost:4000
conduit reload                                     # hot-reload the config
conduit status                                     # uptime, version, in-flight count
conduit status --upstream                          # upstream health table
conduit upstreams add --route /api --target http://new-backend:4000

# or call the API directly (with a token configured, add -H "Authorization: Bearer $ADMIN_TOKEN")
curl http://localhost:2019/status
curl -X POST http://localhost:2019/reload
curl -X DELETE "http://localhost:2019/cache/purge?url=https://example.com/api/data"
curl -X POST http://localhost:2019/ip-deny -d '{"cidr":"1.2.3.0/24"}'

→ All endpoints with request and response examples: docs/admin.md

Benchmarks

Every pull request runs a Performance report in CI that measures reverse-proxy passthrough for the PR head and for its base commit, back to back on the same runner, and posts the difference. It is a trend signal that catches regressions, not a lab measurement, so this README quotes no throughput figure: GitHub-hosted runners land on different CPUs from one run to the next, and throughput depends on the hardware and on global.workers. If you quote a number, quote its setup too. This is the setup CI uses:

   
Runner GitHub-hosted ubuntu-latest: 4 vCPU, 16 GB RAM. The host CPU model differs between runs and is printed in every report.
Conduit cargo build --release with default features (lto = true, codegen-units = 1, stripped), run with the default of one worker thread (global.workers unset)
Upstream a small Go net/http server on port 4000 returning a fixed 22-byte JSON body (source)
Load generator oha 1.16.0: oha -z 10s -c 50 --no-tui --output-format json http://127.0.0.1:8080/
What is compared PR head against PR base, built and measured in the same job

The server configuration, as JSON (what CI writes) and as the equivalent YAML:

{ "port": 8080, "proxy": "http://127.0.0.1:4000" }
port: 8080
proxy: http://127.0.0.1:4000

Throughput grows with global.workers, up to what the machine can give. For scripts that measure requests per second and CPU cost per request on your own hardware, see scripts/bench/; docs/benchmarks.md has the step-by-step reproduction, the reason CI uses one worker, and how to compare Conduit with another proxy fairly.

Limits you should know about

Editor integration (JSON Schema)

Conduit ships a JSON Schema for completion, hover docs and inline validation in JSON and YAML configs.

VS Code — JSON: add one line to your config:

{
  "$schema": "https://raw.githubusercontent.com/lopatnov/conduit/main/schema/conduit.schema.json",
  "port": 3000
}

VS Code — JSON: workspace-wide (all conduit*.json files):

// .vscode/settings.json
{
  "json.schemas": [{
    "fileMatch": ["conduit.json", "conduit.*.json"],
    "url": "https://raw.githubusercontent.com/lopatnov/conduit/main/schema/conduit.schema.json"
  }]
}

VS Code — YAML: add to .vscode/settings.json (requires the YAML extension):

// .vscode/settings.json
{
  "yaml.schemas": {
    "https://raw.githubusercontent.com/lopatnov/conduit/main/schema/conduit.schema.json":
      ["conduit.yaml", "conduit.yml", "conduit.*.yaml"]
  }
}

IntelliJ / WebStorm: Settings → Languages & Frameworks → Schemas and DTDs → JSON Schema Mappings → add the URL https://raw.githubusercontent.com/lopatnov/conduit/main/schema/conduit.schema.json with the file pattern conduit*.json, conduit*.yaml.

Contributing

Contributions are welcome. Read CONTRIBUTING.md before opening a PR.

License

Apache 2.0 © 2024–2026 Oleksandr Lopatnov