Live for Craft CMS

Live blogging for Craft CMS 5

Press ⌘↵. It's on the site.

Live is live blogging for Craft: a composer that publishes in one keystroke, and a feed that updates itself over the top of a page nobody re-rendered. No entry save, no cache purge, no PHP process per reader.

Live

Publish, and watch a cached page catch up

On the left, the composer. On the right, a reader's page, cached since kick-off and never rendered again. Between them, the one file that changes. Publish something, edit an old update, delete one, pin another — then wait for the next poll. Turn off the wifi and publish anyway, or have Alex publish in the same millisecond as you.

Entries → Arsenal v Spurs → Commentary
example.com/live/arsenal-v-spurs
head.json — the only file that changes

            
What the reader asked for

    This runs the plugin's own rules in your browser: sequence numbers allocated on arrival, revisions bumped on edit, tombstones on delete, and a reader that fetches only what it is missing. End the post and the reader stops asking.

    Built for being read by everyone at once

    A live blog is written forty times an hour by someone watching something happen, and read by everybody at the same moment. Almost nothing else on a Craft site behaves like that.

    An update is its own element

    One lightweight save: no revisions, no provisional draft, no search indexing, and the parent entry is never touched. Two journalists on one match are creating separate elements, so neither can overwrite the other.

    Order from a sequence, not a clock

    Each update gets a per-post number allocated under a row lock. Two editors publishing in the same millisecond get 412 and 413, and a reader holding 412 asks for "everything after 412" — a question with one right answer.

    Delivery is static files

    Each publish writes an immutable JSON file for the update and rewrites one small head.json. Readers poll head; nginx or your CDN answers without waking Craft.

    • Every reader in the same poll window asks for the same URL
    • One command rebuilds the files from the database

    The page cache is never invalidated

    The HTML page can be cached for a day, by Craft, Blitz, Varnish or a CDN. Updates arrive over the top of it. That is the point.

    One card, rendered once

    Updates carry HTML rendered by your own live/_update.twig at publish time, so an appended update is byte-identical to a server-rendered one. There is no second copy of the card in JavaScript to drift.

    A composer for people watching something

    A textarea, Markdown and a button per update type. ⌘↵ publishes; a dropped connection queues the update and retries; an edit stays where it is and quietly replaces the reader's copy.

    • Update types with their own field layouts — a Goal asks for a scorer
    • Pinning, key moments, and upcoming / live / paused / ended

    Pro: a newsroom on top

    Who else has the post open. Updates written now and released at 14:00 by your cron. Server-sent events for the internal dashboard. Throttled CDN purging for Cloudflare, Fastly or a webhook.

    Pro: GraphQL shaped like the client

    Poll liveFeed — one indexed row — and fetch liveUpdates(since: 412) only when the sequence moves. Each update type is its own schema component, so a token gets the commentary without the newsroom's internal feed.

    One include

    Server-rendered on first paint, updating itself afterwards. Copy the partials into your own templates to take over the markup — Craft looks there first.

    twig
    {% include 'live/_feed.twig' with { feed: entry.commentary } %}
    
    {# Or build it yourself #}
    {% set feed = entry.commentary %}
    
    {{ feed.state }}      {# upcoming | live | paused | ended #}
    {{ feed.count }}
    {{ feed.pinned }}     {# the pinned update, or null #}
    
    {% for goal in feed.updates.type('goal').all() %}…{% endfor %}
    {% for key in feed.highlights.all() %}…{% endfor %}
    
    {# feed.seq changes on every publish and nothing else #}
    {% cache using key "feed-#{entry.id}-#{feed.seq}" %}…{% endcache %}

    What a reader asks for

    A few hundred bytes of static JSON, every few seconds. When seq moves, the reader fetches the immutable file for each update it is missing; when a rev moves, it replaces that update in place.

    json
    GET /live-feed/1/923-14/head.json?t=357211
    
    {
      "seq": 413,
      "state": "live",
      "count": 218,
      "pinned": 8811,
      "poll": 5,
      "updates": [ { "id": 8940, "seq": 413, "rev": 0 }, … ],
      "removed": [ 402 ]
    }
    
    GET /live-feed/1/923-14/u-413.json?r=0

    Frequently Asked Questions

    The questions worth answering before you install it.

    Start free, upgrade for the newsroom

    Lite is free and is a complete live blog. Pro is $99 with a $79/year renewal, and adds presence, scheduled updates, server-sent events, CDN purging and GraphQL.