A lightweight local proxy that lets you mock API responses without touching your app's code.
Start Ditto, point your app at it, and configure mocks from the built-in dashboard. Matched requests get a fake response instantly. Everything else is forwarded to your real backend.
Download the latest build for your platform from the Releases page.
Download the .zip for your chip:
- Apple Silicon →
darwin_arm64 - Intel →
darwin_amd64
Double-click to extract, then open Ditto.app.
First launch only. macOS blocks unsigned apps by default:
- Right-click (or Control+click) on
Ditto.app→ Open → Open in the dialog. - If macOS still blocks it, run this in Terminal and try again:
xattr -cr /path/to/Ditto.app
Subsequent launches open with a normal double-click.
Extract the .tar.gz and run ./ditto.
- amd64 → full desktop app (requires
libgtk-3andlibwebkit2gtk-4.0) - arm64 → headless mode; opens the dashboard in your browser
Extract the .zip and run ditto.exe. The desktop app opens automatically.
Requires Go 1.26+ and Node.js 20+.
git clone https://github.com/Drafteame/ditto.git
cd ditto
cd frontend && npm install && npm run build && cd ..
go build -o ditto .Ditto can also stand in for your app's WebSocket backend. The minimum to start mocking events:
- Point your app at Ditto. Set its WebSocket URL to
ws://localhost:8888/__ditto__/socket(orwss://...if you started Ditto with--https). Ditto exposes/__ditto__/wsas an alias. - Open the Sockets tab in the dashboard. Connected clients and the channels they have subscribed to appear there in real time.
- Pick a protocol adapter. Choose a built-in (
rawfor plain JSON,appsyncfor AWS AppSync Events) or any adapter profile loaded fromadapter_profiles/(e.g.appsync-drafteaships as a default for AppSync backends with a Draftea-style custom envelope). Profiles are JSON config files that customise envelope shape and type aliases per backend with no Go code — see the Adapter profiles section of the architecture doc. A visual editor for profiles is planned (M9 in the roadmap). - Dispatch an event. Select a channel the client is subscribed to, write the JSON payload, and click Dispatch. The client receives it immediately.
Default adapter profiles are seeded once and then treated as user-owned files. To regenerate the bundled default after removing or renaming it, move custom profile JSON files aside, delete adapter_profiles/.seeded, and restart Ditto.
That's enough to send arbitrary events to a connected app. The features below are optional layers on top:
- Protobuf payloads — upload a
.protoschema pack from the Sockets tab. Pick a type from the dropdown and edit JSON with schema-aware autocomplete; Ditto serializes to Protobuf at dispatch time. No codegen. - Reusable events — save a composed event as a template with
{{vars}}substitution. See docs/EVENT_TEMPLATES.md. - Timed sequences — chain templates into a timeline with delays and play/pause/scrub/speed transport controls. See docs/EVENT_SEQUENCES.md.
To passthrough or record traffic against a real backend, set the upstream WebSocket URL with --live-target. The minimum CLI to launch Ditto wired up for WebSocket work:
# HTTP backend + WS upstream
./ditto --target https://api.example.com --live-target wss://api.example.com/socketThe flag is also available in headless mode and is persisted to config.json, so subsequent launches pick it up. You can also set/change it at runtime from the dashboard's Live Target settings.
Per-channel modes (mock, live, record, mixed) are configured from the Sockets tab. See docs/CHANNEL_MODES.md and docs/RECORDINGS.md for the on-disk recording format.
Full roadmap and milestone-by-milestone breakdown: see the v1.8 section in ROADMAP.md. Architecture, filesystem layout, and adapter profile spec: docs/WEBSOCKET_ARCHITECTURE.md.
For CI, servers, or terminal-only workflows. Runs without the desktop window but keeps the REST API available.
# Minimal: just run the proxy
./ditto --headless
# With a backend target — unmatched requests are forwarded
./ditto --headless --target https://api.example.com
# Machine-readable logs (one JSON object per request on stdout, banner on stderr)
./ditto --headless --log-format json --target https://api.example.com 2>/dev/null| Flag | Default | Description |
|---|---|---|
--port |
8888 |
Port to listen on |
--target |
(none) | Backend URL to forward unmatched requests to |
--live-target |
(none) | WebSocket upstream URL for live/record/mixed channel modes |
--mocks |
(persistent app data) | Directory containing mock JSON files |
--headless |
false |
Run without the desktop window (API stays available) |
--log-format |
text |
Log format: text or json |
--https |
false |
Serve over HTTPS using a self-signed certificate (see below) |
--certs |
./certs |
Directory to store the generated TLS certificate |
--version |
— | Print the version and exit |
Sample JSON log line:
{"timestamp":"22:41:25","type":"MOCK","method":"GET","path":"/users","status":200,"duration_ms":0,"response_body":"..."}The recommended setup is plain HTTP with a debug-only network config in your app. If you need HTTPS instead (app can't be modified, strict requirements, etc.), see docs/HTTPS.md.
See ROADMAP.md.
MIT