Your work stays in this browser.Your code, sketches, and settings are saved only to this browser’s local storage — we never store them on a server. Usage stats are anonymous and aggregate, and pages elsewhere on the site may carry third-party ads. What we collect.

Scritch

The .scritch scene format

A Scritch drawing is plain JSON. You can write one by hand, generate it from a script, or have a model emit it — then drop the file on the canvas and keep editing. This page is the whole format.

Start from a working example

flowchart.scritch is a complete diagram — bound elbow arrows, labels on shapes and on arrows, a wrapped caption. Download it, drop it on the canvas, then open it in an editor to see how each piece is written.

Getting a file onto the canvas

There are three ways in, and they all run the same validation:

  • The import button in the toolbar — the one beside Download. It works on an empty canvas.
  • Drag and drop a .scritch file anywhere onto the canvas.
  • Sketches menu (the folder icon) → “Open .scritch file…”. The same menu has “Save as .scritch file” to get one back out.
  • Scripted — window.scritch.load(), covered at the bottom.

Opening a file replaces the canvas, but it lands on the undo stack — ⌘Z brings your drawing back.

The smallest useful file

A bare array of shapes is accepted, so the shortest thing that draws something is one line. Ids, colours and stroke widths all have defaults.

[
  { "type": "rect", "x": 0, "y": 0, "w": 160, "h": 80 }
]

The full wrapper adds a type tag and a version, and is what “Save as .scritch file” writes:

{
  "type": "scritch",
  "version": 1,
  "shapes": [
    { "id": 1, "type": "rect", "x": 40, "y": 40, "w": 200, "h": 100,
      "color": "#3b82f6", "width": 4 },
    { "id": 2, "type": "text", "text": "for each card",
      "size": 22, "color": "#3b82f6", "containerId": 1 },

    { "id": 3, "type": "diamond", "x": 480, "y": 260, "w": 220, "h": 140,
      "color": "#f97316", "width": 4 },
    { "id": 4, "type": "text", "text": "threw?",
      "size": 22, "color": "#f97316", "containerId": 3 },

    { "id": 5, "type": "arrow", "x1": 240, "y1": 90, "x2": 480, "y2": 330,
      "color": "#64748b", "width": 4,
      "startBinding": 1, "endBinding": 3, "route": "orthogonal" },
    { "id": 6, "type": "text", "text": "next",
      "size": 18, "color": "#64748b", "containerId": 5 }
  ]
}

That example is worth reading twice: the two labels have no coordinates because containerId centres them in their box, and the arrow needs no elbow maths because startBinding/endBinding plus route let the renderer work out the path.

Every shape

Each entry needs a type. Coordinates are world pixels — y grows downwards, and there is no canvas size to stay inside.

On any shape
idint
Unique. Referenced by bindings, labels and groups. Assigned for you if you leave it out.
colorstring
Stroke/text ink. #rgb, #rrggbb(aa), rgb()/rgba(). Defaults to the theme ink.
bgColorstring | null
Fill for a closed shape, painted as a light wash of the colour.
fillStyleenum
solid · hachure · cross-hatch
widthnumber
Stroke width in px.
strokeStyleenum
solid · dashed · dotted
opacity0–1
Defaults to 1.
rotationradians
About the shape’s centre.
groupIdint
Shapes sharing one move and select together.
lockedboolean
Not selectable until unlocked from the right-click menu.
Boxes — rect · ellipse · diamond · triangle
x, y, w, hnumberrequired
Top-left corner and size. Required.
Connectors — arrow · line
x1, y1, x2, y2numberrequired
The two endpoints. Required even when bound — they’re where the connector starts from.
startBinding, endBindingint | null
Ids of the shapes the ends attach to. A bound end follows its shape when you move it, and gives an elbow route the box geometry to leave from.
routeenum
direct (default) draws a straight or hand-bent line. orthogonal draws right-angle elbows that leave and enter bound boxes perpendicular to an edge and step around other boxes in the way.
bend, bendAlongnumber
Curve the shaft: perpendicular and along-line offset of its apex. Ignored by an orthogonal route.
startCap, endCapenum
Crow’s-foot cardinality for ER diagrams: none · one · many · zero-one · one-many · zero-many. An end cap replaces the arrowhead.
text
textstringrequired
The content. \n forces a line break.
sizenumber
Font size in px.
fontFamilyenum
code (default) · sans · mono · hand
x, ynumber
Top-left origin, for a free-standing label.
containerIdint
Makes this the label of another shape. Drop x/y: it centres in a box (shrinking to fit) or sits in a gap in a connector’s shaft.
boxWnumber
Wrap boundary for a free-standing label, in px — the text flows and wraps inside this width instead of running on. Drag a label’s side handle on the canvas to set it visually.
anchor{ nx, ny }
For a container label, where it sits in the box as a 0–1 fraction. Omit for dead-centre.
path · eraser
points[{ x, y }]required
A freehand stroke. An eraser stroke cuts through what’s beneath it.
image
x, y, w, hnumberrequired
Placement box.
srcdata URLrequired
A data:image/png|jpeg|gif|webp;base64,… URL. Remote URLs and SVG data URLs are refused.

Everything the canvas can do, and how to write it

Anything you can draw or set in the UI has a field here — with two exceptions noted below, the file is the complete picture. Working the other way, a few things are only reachable by writing the file.

Tools → shapes
Rectangle · Ellipse · Triangle · Diamondtype
rect · ellipse · triangle · diamond, each with x, y, w, h.
Line · Arrowtype
line · arrow with x1, y1, x2, y2. Drawing one from a shape to a shape is what sets startBinding/endBinding.
Pen · Erasertype
path · eraser with points.
Texttype
text. Double-clicking a shape instead of the canvas is what sets containerId.
Paste or drop an imagetype
image. The app downscales and embeds it as a data URL, which is what src holds.
Eyedropper—
Copies styling between shapes. Nothing to store — it just writes the same style fields.
Style panel → fields
Stroke · Backgroundcolor, bgColor
The palette resolves to hex before it’s stored, so a file names colours directly.
Stroke width · Stroke stylewidth, strokeStyle
Fill stylefillStyle
Solid, hachure or cross-hatch, used when bgColor is set.
Routeroute
Direct or orthogonal — see the connector fields above.
Connector endsstartCap, endCap
Font size · Fontsize, fontFamily
Opacityopacity
Canvas actions → fields
Bring to front / Send to backarray order
There is no z-index. Shapes paint in array order, so later in shapes is on top — reordering the array is reordering the drawing.
Group / UngroupgroupId
Any int; shapes sharing one select and move together.
Locklocked
Rotaterotation
Radians, clockwise, about the shape’s centre.
Align / Distributex, y
Pure geometry — the UI just computes coordinates, so a generator writes the result directly.
Drag a label’s side handleboxW
Sets the wrap boundary described above.
Drag an arrow’s midpointbend, bendAlong
Only by writing the file
sloppinessnumber
0 is the clean stroke the UI draws. 1 or 2 render hand-drawn and progressively rougher — there’s no control for this on the canvas.
seednumber
Fixes the randomness of a sketchy stroke, so re-exporting gives a byte-identical result.
anchor{ nx, ny }
Places a container label off-centre at an exact fraction of its box, rather than wherever you clicked.

What a scene file doesn’t hold

A .scritch file is the drawing, not the session. These live in your browser and are deliberately left out, so opening someone’s file can’t disturb your setup:

  • Pan and zoom. Opening a file frames the drawing for you; load(scene, { fit: false }) leaves the view alone.
  • Saved sketches and their names. Those are a browser-local library — a file is one drawing. Use the Sketches menu to name and keep it.
  • Tool and panel state — the selected tool, snap-to-grid, canvas lock, and the styles queued up for the next shape you draw.
  • Presentation mode and any laser marks made during it. Marks are never persisted at all.
  • Undo history. Opening a file is itself one undoable step.

Sharing the whole thing instead? The share button packs the same shapes into a link, and the workspace backup in the top bar exports every sketch, snippet and setting at once.

What gets refused, and why

Import is strict on purpose, and it tells you what it did rather than failing silently. A shape is dropped if its type is unknown, its required geometry is missing, or it reuses an id. A field is stripped if it isn’t in this page or its value is the wrong type — so a typo means a missing colour, never an unnoticed one.

  • Colours must match the notations above. A colour is written straight into an SVG attribute when you export, so free-form strings aren’t accepted.
  • Images must be raster data URLs. An image/svg+xml data URL can carry script that would run when someone opens your exported SVG.
  • A binding or containerId pointing at a shape that didn’t survive is cleared, so nothing dangles.
  • Up to 5,000 shapes per file. A file claiming a version newer than 1 is refused rather than half-read.

Driving the canvas from code

While /scritch is open it exposes window.scritch. Everything goes through the same validation and the same canvas state as the UI — no storage key to guess at, and no reload.

// On /scritch, in the console or a Playwright script:
await new Promise((r) => (window.scritch ? r() : setTimeout(r, 100)));

window.scritch.load(scene);              // replace the canvas (undoable)
const png = await window.scritch.exportPng({ scale: 2 });
const svg = await window.scritch.exportSvg();

window.scritch.save();                   // -> a .scritch scene object
window.scritch.getShapes();              // -> the raw shapes array
window.scritch.bounds(3);                // -> a shape's box (or the scene's)
window.scritch.route(5);                 // -> an elbow route's points
window.scritch.clear();
window.scritch.fit();

load() takes a scene object, a bare shapes array, or the JSON text of either, and returns { ok, count, dropped, strippedFields } so you can see what it refused. Exports always frame the whole drawing, never the visible viewport — the scale option multiplies the bitmap, and is lowered automatically if the result would exceed what a browser canvas can hold.

← Back to the canvas