Skip to content

Visualize Code Changes with OpenClaw Diffs Plugin

Ever had to squint at a wall of + and - signs in a terminal or chat window just to figure out what your agent actually changed? It is frustrating when you just want to see a clean, readable comparison of your code.

The diffs plugin solves this by turning messy change content into a clean, read-only diff artifact. You can get a URL to view the changes in a browser or a rendered PNG/PDF file to send directly in a chat.

Getting started is simple. You just need to follow these steps:

  1. Enable the plugin.
  2. Call diffs with mode: "view" for canvas-first flows.
  3. Call diffs with mode: "file" for chat file delivery flows.
  4. Call diffs with mode: "both" when you need both artifacts.

To get started, add this to your configuration:

{
plugins: {
entries: {
diffs: {
enabled: true,
},
},
},
}

If you want to keep the diffs tool enabled but disable its built-in system-prompt guidance, set plugins.entries.diffs.hooks.allowPromptInjection to false:

{
plugins: {
entries: {
diffs: {
enabled: true,
hooks: {
allowPromptInjection: false,
},
},
},
},
}

This blocks the diffs plugin’s before_prompt_build hook while keeping the plugin, tool, and companion skill available. If you want to disable both the guidance and the tool, disable the plugin instead.

Here is how your agent usually handles the process:

  1. Your agent calls diffs and then reads the details fields.
  2. The agent then chooses to open the details.viewerUrl with canvas present, send the details.filePath with a message, or do both.

You can provide the content in two ways. First, using before and after text:

{
"before": "# Hello\n\nOne",
"after": "# Hello\n\nTwo",
"path": "docs/example.md",
"mode": "view"
}

Or you can use a unified patch:

{
"patch": "diff --git a/src/example.ts b/src/example.ts\n--- a/src/example.ts\n+++ b/src/example.ts\n@@ -1 +1 @@\n-const x = 1;\n+const x = 2;\n",
"mode": "both"
}

All fields are optional unless noted:

  • before (string): original text. Required with after when patch is omitted.
  • after (string): updated text. Required with before when patch is omitted.
  • patch (string): unified diff text. Mutually exclusive with before and after.
  • path (string): display filename for before and after mode.
  • lang (string): language override hint for before and after mode.
  • title (string): viewer title override.
  • mode ("view" | "file" | "both"): output mode. Defaults to plugin default defaults.mode.
  • theme ("light" | "dark"): viewer theme. Defaults to plugin default defaults.theme.
  • layout ("unified" | "split"): diff layout. Defaults to plugin default defaults.layout.
  • expandUnchanged (boolean): expand unchanged sections when full context is available. Per-call option only (not a plugin default key).
  • fileFormat ("png" | "pdf"): rendered file format. Defaults to plugin default defaults.fileFormat.
  • fileQuality ("standard" | "hq" | "print"): quality preset for PNG or PDF rendering.
  • fileScale (number): device scale override (1-4).
  • fileMaxWidth (number): max render width in CSS pixels (640-2400).
  • ttlSeconds (number): viewer artifact TTL in seconds. Default 1800, max 21600.
  • baseUrl (string): viewer URL origin override. Must be http or https, no query/hash.

Validation and limits:

  • before and after each max 512 KiB.
  • patch max 2 MiB.
  • path max 2048 bytes.
  • lang max 128 bytes.
  • title max 1024 bytes.
  • Patch complexity cap: max 128 files and 120000 total lines.
  • patch and before or after together are rejected.
  • Rendered file safety limits (apply to PNG and PDF):
    • fileQuality: "standard": max 8 MP (8,000,000 rendered pixels).
    • fileQuality: "hq": max 14 MP (14,000,000 rendered pixels).
    • fileQuality: "print": max 24 MP (24,000,000 rendered pixels).
    • PDF also has a max of 50 pages.

The tool returns structured metadata under details.

Shared fields for modes that create a viewer:

  • artifactId
  • viewerUrl
  • viewerPath
  • title
  • expiresAt
  • inputKind
  • fileCount
  • mode

File fields when PNG or PDF is rendered:

  • filePath
  • path (same value as filePath, for message tool compatibility)
  • fileBytes
  • fileFormat
  • fileQuality
  • fileScale
  • fileMaxWidth

Mode behavior summary:

  • mode: "view": viewer fields only.
  • mode: "file": file fields only, no viewer artifact.
  • mode: "both": viewer fields plus file fields. If file rendering fails, viewer still returns with fileError.
  • The viewer can show rows like N unmodified lines.
  • Expand controls on those rows are conditional and not guaranteed for every input kind.
  • Expand controls appear when the rendered diff has expandable context data, which is typical for before and after input.
  • For many unified patch inputs, omitted context bodies are not available in the parsed patch hunks, so the row can appear without expand controls. This is expected behavior.
  • expandUnchanged applies only when expandable context exists.

You can set plugin-wide defaults in ~/.openclaw/openclaw.json:

{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
defaults: {
fontFamily: "Fira Code",
fontSize: 15,
lineSpacing: 1.6,
layout: "unified",
showLineNumbers: true,
diffIndicators: "bars",
wordWrap: true,
background: true,
theme: "dark",
fileFormat: "png",
fileQuality: "standard",
fileScale: 2,
fileMaxWidth: 960,
mode: "both",
},
},
},
},
},
}

Supported defaults include fontFamily, fontSize, lineSpacing, layout, showLineNumbers, diffIndicators, wordWrap, background, theme, fileFormat, fileQuality, fileScale, fileMaxWidth, and mode. Explicit tool parameters override these defaults.

You can control remote access with this setting:

{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
security: {
allowRemoteViewer: false,
},
},
},
},
},
}
  • security.allowRemoteViewer (boolean, default false)
    • false: non-loopback requests to viewer routes are denied.
    • true: remote viewers are allowed if tokenized path is valid.
  • Artifacts are stored under the temp subfolder: $TMPDIR/openclaw-diffs.
  • Viewer artifact metadata contains a random artifact ID, a random token, createdAt, expiresAt, and the stored viewer.html path.
  • Default viewer TTL is 30 minutes when not specified.
  • Maximum accepted viewer TTL is 6 hours.
  • Cleanup runs after artifact creation.
  • Expired artifacts are deleted.
  • Fallback cleanup removes stale folders older than 24 hours when metadata is missing.

Viewer route:

  • /plugins/diffs/view/\{artifactId}/\{token}

Viewer assets:

  • /plugins/diffs/assets/viewer.js
  • /plugins/diffs/assets/viewer-runtime.js

URL construction behavior:

  • If baseUrl is provided, it is used after strict validation.
  • Without baseUrl, viewer URL defaults to loopback 127.0.0.1.
  • If gateway bind mode is custom and gateway.customBindHost is set, that host is used.

baseUrl rules:

  • Must be http:// or https://.
  • Query and hash are rejected.
  • Origin plus optional base path is allowed.

Viewer hardening:

  • Loopback-only by default.
  • Tokenized viewer paths with strict ID and token validation.
  • Viewer response CSP blocks outbound connect-src and only allows scripts and assets from self.
  • Remote miss throttling when remote access is enabled includes 40 failures per 60 seconds and a 60 second lockout.

File rendering hardening:

  • Screenshot browser request routing is deny-by-default.
  • Only local viewer assets from http://127.0.0.1/plugins/diffs/assets/* are allowed.
  • External network requests are blocked.

mode: "file" and mode: "both" need a Chromium-compatible browser. The system looks for the browser in this order:

  1. browser.executablePath in OpenClaw config.
  2. Environment variables like OPENCLAW_BROWSER_EXECUTABLE_PATH, BROWSER_EXECUTABLE_PATH, or PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH.
  3. Platform command/path discovery fallback.

If you see a message saying Diff PNG/PDF rendering requires a Chromium-compatible browser, you can fix it by installing Chrome, Chromium, Edge, or Brave.

Input validation errors:

  • Provide patch or both before and after text. (Include both before and after, or provide patch).
  • Provide either patch or before/after input, not both. (Do not mix input modes).
  • Invalid baseUrl: ... (Use http(s) origin with optional path, no query/hash).
  • \{field} exceeds maximum size (...) (Reduce payload size).
  • Large patch rejection (Reduce patch file count or total lines).

Viewer accessibility issues:

  • Viewer URL resolves to 127.0.0.1 by default.
  • For remote access, pass baseUrl per tool call or use gateway.bind=custom.
  • Enable security.allowRemoteViewer only when you intend external viewer access.

Other issues:

  • Unmodified-lines row has no expand button: This is expected for patch input when the patch does not carry expandable context.
  • Artifact not found: The artifact might have expired due to TTL or been removed by cleanup.
  • Prefer mode: "view" for local interactive reviews in canvas.
  • Prefer mode: "file" for outbound chat channels that need an attachment.
  • Keep allowRemoteViewer disabled unless your deployment requires remote viewer URLs.
  • Set explicit short ttlSeconds for sensitive diffs.
  • Avoid sending secrets in diff input when not required.
  • If your channel compresses images aggressively, prefer PDF output (fileFormat: "pdf").

The diff rendering engine is powered by Diffs.

AI Setup Assistant

OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.