Skip to main content
Last updated: 23 Nov 2023

whitehall: How does Whitehall handle contact expansion in govspeak?

See also:


Whitehall allows publishers to reference contact information inline in govspeak, using syntax like this:

You can send a letter to the Government Digital Service at:


This will be automatically expanded to the full address (The White Chapel Building, 10 Whitechapel High Street…) when the document is published.

If the user puts an ID that doesn't exist in the [Contact:XXX] expression, it will be silently removed from the output.

Any documents referencing this contact will be automatically republished whenever the contact is updated.

If a contact is deleted, documents referencing that contact won't be automatically updated, but when they're next published they'll switch to silently removing the contact block, as the ID is no longer referenced. This is probably a bug, but it hasn't been noticed because contacts are rarely deleted.

A similar approach is used to allow editions to link to other editions which are still in draft, and automatically handle updates to the URL path (/ slug).

How does a document know which contacts it references?

There's a ContactsExtractor class, which scans govspeak for elements matching the /[Contact:([0-9]+)]/ regex.

This is used to pull out a list of dependant contacts, which are stored on the Edition by the EditionDependenciesPopulator. This code is subscribed to publish / withdraw events.

When an Edition is published, we call render_embedded_contacts, which finds the dependencies again and expands them.

Ultimately, the Edition model has a has_many relationship with Contacts through an EditionDependencies table.

How does the code know to republish dependant documents when a contact changes?

There's a Dependable module, which adds a "has_many :dependent_editions" relation to models.

The Contact model has an after_update callback which calls republish_dependent_editions (provided by the Dependable module). This sends each dependent edition to publishing-api, which involves expanding the contacts in govspeak.

Notes on this architecture

  1. Changes to contacts which trigger updates to dependent Editions don't appear in the history of those Editions - there are no change notes, or records of any kind showing the change.
  2. Contacts themselves aren't edition-able, so there's no way to create a circular dependency (although there is for linked draft editions, but that's a separate topic)