Canva Post-Trained an Open Model and Cut Edit Cost 46%. The Playbook Travels

·11 min read·Evergreen Tools Team

On October 29, 2026, Canva announced Brush, a vision-language model for design built by Canva Research — a 160-person AI lab — and shipped it into Canva AI for Pro subscribers. Two numbers in the announcement carry the story: up to 69% faster on general design editing tasks, and 46% lower cost per edit task than Canva's previous design agent. That is not a leaderboard improvement. That is a unit-economics improvement, and it is the kind that decides whether a consumer product can afford to give the feature away. The interesting part for anyone building with models is how ordinary the recipe is: start from an open-weights base, post-train on real edit tasks, then push it with large-scale reinforcement learning.

Layouts and colour palettes on a design desk

Generation has freedom. Editing has to be constrained: change this, touch nothing else.

1. What Brush does, and what it replaced

Brush reads a design — layout, layers, overall vibe — and makes the change you asked for, from a single line of copy to every slide in a deck. It lands one week after Canva Craft, the company's image-generation model, which Canva says made it the second most advanced AI lab for image generation on the DesignArena leaderboard. The division of labour is clean: Craft generates, Brush edits the design around what was generated. Canva's framing is the right one — great design happens in the edit. Generating something presentable is hard. Editing something existing without wrecking it is harder.

2. Why editing is the harder problem

Generation starts from nothing, so the model has total freedom. Editing does not: it has to understand what you want changed, how the design is constructed, and — the part everyone underestimates — what must stay exactly as it is. Ask for a bigger headline and a careless model happily reflows the entire layout while it is at it. So the core metric for an editing product is not "does the result resemble the request". It is "did it change the right things and leave the rest alone". That constraint cannot live in a prompt. It has to live in a check.

# A design is a tree. An edit that touches one text node must not silently
# reflow the other eleven. Diff the tree before and after and fail loudly.
import json

def nodes(doc, prefix=""):
    for child in doc.get("children", []):
        nid = f"{prefix}/{child['id']}"
        yield nid, child
        yield from nodes(child, nid)

def scope_violations(before, after, allowed_ids):
    b, a = dict(nodes(before)), dict(nodes(after))
    bad = []
    for nid in set(b) & set(a):
        if nid in allowed_ids:
            continue
        if json.dumps(b[nid], sort_keys=True) != json.dumps(a[nid], sort_keys=True):
            bad.append(nid)
    bad += [f"deleted:{n}" for n in sorted(set(b) - set(a))]
    return bad

# This check is why "make the title bigger" does not turn into "make the title
# bigger and also restyle every slide". Boring, deterministic, and load-bearing.

3. What the 46% actually measures

46% is cost per edit task; 69% is speed on general edits. For a product serving hundreds of millions of people and billions of designs, a few extra cents per task compounds into a hole in the P&L. Canva states the motive plainly: the efficiency gains let it deliver a stronger Canva AI experience to far more people while making it more affordable. Read that as the only viable path for consumer AI — drive unit cost down until the feature can be given away, because pricing it per use puts it out of reach for most of the audience you were building for.

# The reward signal is the product decision. Canva had to define what makes a
# design good before it could train against it, and so do you.
# Helper functions (type_scale, palette, coverage, contrast) omitted on purpose.
def reward(before, after, brief):
    score = 0.0

    # Brand consistency: the type scale and palette come from the source doc.
    score += 2.0 * (type_scale(after) == type_scale(before))
    score += 2.0 * (set(palette(after)) <= set(palette(before)))

    # Instruction coverage: everything the brief names should actually change.
    score += 3.0 * coverage(after, brief.targets)

    # Collateral damage: untouched nodes must stay byte-identical.
    score -= 5.0 * len(scope_violations(before, after, brief.allowed))

    # Legibility floor: not negotiable, and not something to learn from votes.
    score -= 10.0 * count_contrast_failures(after)

    return score

4. The training recipe: open base, post-train, then RL

Brush started from a leading open-weights model and was post-trained on real designs and real editing tasks, using design expertise plus anonymised insights from people who opted in to improve Canva AI. On top of that sits large-scale reinforcement learning, where the model works through complete design and editing tasks, learns from what worked, and reshapes its behaviour. The load-bearing piece is the reward system: before Canva could train against "a great design", it had to define one. Brand consistency, instruction coverage, collateral damage, legibility — features you can actually compute are the skeleton of a design reward, and everything else is opinion.

# The loop worth copying: act with code, then look at the page. An agent that
# cannot see the result of its own edit is guessing.
def edit_loop(design, brief, model, max_turns=4):
    history = []
    for _ in range(max_turns):
        action = model.propose(design, brief, history=history)
        result = run_in_sandbox(action.code, working_copy=design)

        if result.error:
            history.append(f"runtime error: {result.error}")
            continue

        shot = render(design)                 # screenshot the actual page
        verdict = verify(shot, brief)         # cheap, deterministic checks
        if verdict.ok:
            return design

        history.append(verdict.summary)       # "title overflows frame 3"
    return design                             # ship last good state, still editable

5. The agentic loop: write code, then look at the page

Brush runs an agentic loop that writes and runs code to adjust design elements, then inspects what is happening on the page to guide the next step. That observe-after-acting structure is what makes multi-page consistency and format adaptation tractable: the model is not guessing about the result, it is looking at it. The other decision worth stealing is that output stays fully editable — text, imagery, colour, layout, all of it. A rendered flat image would be cheaper and would not survive contact with a real design workflow, where the designer keeps editing, the client rewrites copy, and marketing needs ten sizes by Friday.

# "46% cheaper per edit" is a unit-economics claim, so measure it as one.
def cost_per_edit(runs, price_in, price_out, gpu_usd_per_second):
    gen = sum(r.tokens_in * price_in + r.tokens_out * price_out for r in runs) / 1_000_000
    infra = sum(r.gpu_seconds for r in runs) * gpu_usd_per_second
    retries = sum(max(0, r.turns - 1) for r in runs)
    retry_gen = gen * (retries / max(len(runs), 1))
    return (gen + infra + retry_gen) / len(runs)

# The line that moves when you post-train your own model is tokens and GPU
# seconds. The invoice from a model provider is the line you are trying to
# shrink, and it only shrinks if the task is narrow enough to specialise on.

6. Three questions before you copy this

First, is your task narrow enough? Post-training beats a larger general model on narrow tasks and loses on broad ones. Second, do you have a reward signal? Without a computable definition of good, reinforcement learning amplifies noise and calls it taste — and defining good is a product decision, not an ML one. Third, do you have a verifier? An agent that acts without looking at the result is guessing, and for high-dimensional output like design you need both deterministic checks (contrast, overflow, out-of-bounds) and visual ones. Get all three and train. Miss any of them and spend the quarter on prompts and plumbing instead, which is usually the better trade anyway.

# Head-to-head on the same edit set, with humans voting. Offline similarity
# will not tell you whether the design is usable.
def pairwise_wins(old_agent, new_agent, cases, raters):
    wins = total = 0
    for case in cases:
        a = old_agent.edit(case.design, case.brief)
        b = new_agent.edit(case.design, case.brief)
        for rater in raters:
            if rater.prefers(b, a, case.brief):
                wins += 1
            total += 1
    return wins / total

# Report it next to the share of edits a user accepts without a second pass.
# That second number is what the product actually sells, and it is the one a
# pure cost optimisation will quietly destroy.
Two design versions compared side by side on screen

For an editing product the metric is not resemblance. It is whether the right things changed and the rest did not.

📌 Frequently Asked Questions

What is Canva Brush, and how is it different from Craft?

Brush is a vision-language model from Canva Research built for design editing: it reads layout, layers and style, then applies the change you asked for to an existing design. Craft, announced October 22, is Canva's image-generation model. In Canva's own framing, Craft generates and Brush refines the design around the generated result.

Faster and cheaper than what, exactly?

Than Canva's previous design agent. The 69% figure is the upper bound on speed for general design editing tasks, and 46% is the reduction in cost per edit task. Both are Canva's internal measurements against its own prior system, not against competitors.

Is Brush built on a proprietary or an open model?

Canva says Brush started from a leading open-weights model, then was post-trained on real design and editing tasks and combined with large-scale reinforcement learning. The base is open weights; the differentiation comes from post-training data, the reward system, and the inference loop.

Why does it write code instead of just emitting an image?

Because design edits need precision: which layer, which value, which properties must not move. Expressing edits as code makes them controllable and verifiable — after execution the agent can inspect the actual page state and decide the next step, which is not possible when the only output is pixels.

Can a small team reproduce this approach?

The approach yes, the scale no. A viable small version: pick an open base, narrow the task to one class of edit, post-train on your own real data, define computable rewards (brand consistency, collateral damage, legibility), and add deterministic verifiers. Running RL without a reward signal and a verifier only produces more confident mistakes.