Live for Craft CMS

Usage

The composer

The composer is the Live field's input. Open an entry that has one and it is there in the editor: a plain textarea, a row of buttons — one per update type — and the feed underneath.

Type, then press ⌘↵ (Ctrl+↵ on Windows and Linux). The textarea accepts Markdown. Anything more elaborate than that gets in the way of somebody who is watching something and typing at the same time.

  • Publishing never saves the entry. Each update is its own element, so two journalists on one match are creating separate elements rather than editing the same one. There is no last write wins and no lost paragraph.
  • A dropout costs you nothing. The card appears straight away and the request goes in the background. An update that fails to send is kept in a local queue in the browser and retried. Press-box wifi is press-box wifi.
  • An edit stays where it is. Click an update's edit action and the composer reopens what was typed. Saving the correction leaves the update in its place in the feed and bumps its revision, so readers holding the old copy quietly get the new one. Esc cancels an edit.
  • There is a full-screen version at Live → Posts, for when the entry edit screen is in the way. It lists every live post with the live ones first.

The composer refreshes every composerPollInterval seconds (8 by default), so editors see each other's updates without reloading.

Who can do what

Live's permissions sit on top of the entry's own:

ToYou need
See the composer and follow the feedView live posts, and permission to view the entry
Publish, edit your own, pinPublish updates, and permission to edit the entry
Edit someone else's updateEdit other people's updates
Delete an updateDelete updates
Start, pause and end the postStart, pause and end live posts, and permission to edit the entry

Update types

Each type is a button in the composer. Open Live → Update Types to add one. A type has:

  • Name — what editors see on the button
  • Handle — how templates and the API refer to it (goal, photo, quote)
  • Description — the button's tooltip
  • Icon and Colour — passed to your templates and the GraphQL API; the colour also marks updates of this type in the composer
  • Show in the composer — off for types only ever created by code or an import
  • Headline field — give updates of this type their own headline. Most live updates don't need one. With it off, a Title format (an object template, like an entry type's) can generate the update's title from its fields — {scorer} {minute}' for a Goal
  • Field layout — whatever the type needs: a Goal with a scorer and a minute, a Photo with an asset, a Quote with who said it

A Live field can be limited to some types in its settings — the commentary might take Goal, Card and Update, while an internal newsroom feed takes something else entirely.

Admin-only, and only where admin changes are allowed

Update types are project config: field layouts and a Twig title format, deployed with the rest of your schema. Like Craft's own entry types they are an admin's to change, and only where allowAdminChanges is on. There is no separate "manage update types" permission, and where admin changes are off, saving a type is refused. Add a type locally, commit config/project/, and deploy it.

Pinning

Tick Pin when publishing, or use an update's pin action afterwards. One update is pinned at a time; pinning another unpins the first in the same write.

The shipped live/_feed.twig shows the pinned update twice on purpose — once at the top, once in its place in the timeline. Both copies carry the same data-seq, so an edit to it updates both.

Key moments

Tick Key moment to mark an update as one of the highlights. That is how you get a summary strip — goals, cards, the result — without writing it twice:

{% for update in entry.commentary.highlights.all() %}
    <a href="#update-{{ update.id }}">{{ update.postedAt|date('H:i') }} {{ update.title }}</a>
{% endfor %}

States

A post is always in one of four states:

StateMeaningReaders
upcomingCoverage hasn't started. The default for a new post.The feed polls, waiting for the first update.
liveCoverage is running. Setting it the first time records startedAt.The feed polls.
pausedHalf time, a rain delay, lunch.The feed keeps polling.
endedIt's over. Records endedAt.The feed stops polling.

Move a post between them from the composer. An ended post can be reopened if it was called too early, which clears endedAt.

Once a post has ended, the page is an ordinary article with the whole commentary rendered into it — the version that gets read for the next two years. Nothing needs to be exported or flattened.

Only as public as the entry

A live blog follows its entry's status on each site. Updates written on an entry that is scheduled, disabled, expired, trashed or still a draft are kept, but they stay off the feed endpoints and out of the static files until the entry goes live. That makes it safe to set up coverage ahead of time on an entry nobody can see yet.

When the entry goes live, the first save, publish or garbage-collection run writes every update to disk at once. Disable, trash or expire it and its files are removed.

Scheduled and embargoed updates (Pro)

Turn on Allow scheduled updates in the field's settings and the composer offers a "post at" time. An update with a time in the future is saved with its sequence number but held back, with a Scheduled status, until it is released.

Releasing is a console command, so the thing that puts an embargoed update on the site is your cron, not somebody remembering:

* * * * * php /path/to/craft live/scheduled/release

Presence (Pro)

With Show other editors on in the field, the composer shows who else has the same post open — "Also here: Sam, Alex". Presence lives in the cache, not the database: it is worth nothing after a minute and a live blog does not need another write per editor every twenty seconds. It is about not covering the same moment twice; the element model already made concurrent writing safe.