Summary: Every name learned is a small act of love, and this recipe is built to make that easy to practice, over and over, until it is a habit. A Lava Application Content block shows a random person's photo and asks the player to guess their name, then swaps in the result and the next question with HTMX, no full page reload and no JavaScript framework. The roster is not hardcoded anywhere. It is built live from Rock Tags, so any ministry staff member can point this at their own people and be playing today.

Full source: github.com/notGerryMiller/photo-rolodex

Why this recipe

Knowing someone's name is one of the simplest things a church can do to make them feel like they belong, and it is one of the hardest things to keep doing as a church grows. Greeters inherit a roster they have not memorized yet. A new staff member starts mid-year surrounded by names they have not learned. A small group leader takes over a group that just merged with another. That is not a person problem. It is a math problem. There are just more names than any one person can hold onto, and the usual fixes, a printed directory, a spreadsheet nobody opens, ask people to do homework for something that is supposed to feel like care.

This recipe turns name-learning into a fast, low-stakes habit instead of a study task: a photo, a guess, a right or wrong answer, repeat. It is built to be handed to anyone doing the work of welcome, greeters, small group leaders, new staff, serving-team volunteers, without asking a developer to build and maintain a custom list for every team.

It also happens to be one of the best ways to actually learn Lava Applications and HTMX, rather than just read about them. Building this recipe walks you through a real Lava Application end to end: two LavaEndpoints, a host page, and HTMX driving every swap in between, no full page reload, no JavaScript framework. The game is the approachable wrapper. The Tag-based roster, the server-side random pick, and the swap-in-place pattern underneath it generalize to far more than a game.

Prerequisites

  • Rock RMS 18.0 or later, no exceptions. That version introduced both the Lava Applications feature and the built-in {[ dropdown ]} shortcode this build depends on. Check Admin Tools > CMS Configuration for a Lava Applications item; if it is not there, this recipe will not run yet.
  • HTMX loaded on the page or in the theme.
  • A pool of people to draw from, each with a profile photo on file.

How the roster is sourced

Most recipes like this ask you to configure a group, a campus, or a data view up front. This one does not. Who is in the game is driven entirely by Rock Tags: tag the people you want, one at a time from a Person profile or in bulk from a Grid, and any active, Person-scoped tag you are authorized to see shows up automatically in the setup screen's dropdown. Adding a new list is a staff action in Rock, not a code change or a redeploy.

The setup endpoint builds that dropdown with the {% tag %} Lava entity command instead of raw SQL:

{% tag where:'EntityType.Guid == "72657ED8-D16E-492E-AC12-144C5E7567E7" && IsActive == true' sort:'Order,Name' %}
  {% assign tags = tagItems %}
{% endtag %}

That choice buys more than tidier syntax. Entity commands check the current person's authorization on every record they return, and security is on by default for every entity except Person. So a personal tag, one owned by a single staff login rather than shared organization-wide, shows up automatically for its owner and stays invisible to everyone else, with no extra permission logic to write. Personal tags stop being a workaround and become a real feature: a small group leader can tag their own group personally and start playing without asking an admin to create anything shared.

People without a profile photo are skipped automatically wherever the deck is built, so the game never shows a blank or placeholder image. If a tagged list looks smaller than expected, that is almost always why.

Building the deck

Once a tag is chosen, the game endpoint builds a shuffled deck from everyone carrying that tag who also has a photo on file and an active record status:

SELECT Id
FROM (
  SELECT DISTINCT p.Id
  FROM TaggedItem ti
  JOIN Person p ON p.Guid = ti.EntityGuid
  WHERE ti.TagId             = @tagId
    AND p.PhotoId             IS NOT NULL
    AND p.RecordStatusValueId = 3
) AS TaggedPeople
ORDER BY NEWID()

The inner query deduplicates in case someone was tagged more than once with the same tag; SQL Server will not allow ORDER BY NEWID() directly against a SELECT DISTINCT, so the random shuffle happens in an outer query that wraps it instead. The resulting Id list becomes the deck, carried through the rest of the game entirely in the query string (deck order, current index, score), so a page reload never depends on server-side session state.

Wiring up HTMX

Every screen after the initial load is a normal link with href="#" and an hx-get attribute pointing at the next fragment, targeting the same container the whole game lives in:

<a href="#" hx-get="{{ nextCardUrl }}" hx-target="#rolodex-app" class="btn btn-success btn-lg btn-block">
  Next <i class="fa fa-arrow-right"></i>
</a>

Rock's Lava Applications support a shorthand for calling one endpoint from another: ^/{applicationSlug}/{endpointSlug} resolves to the correct API path without hand-building it, which is what keeps the setup and game endpoints decoupled from a hardcoded URL.

Security note: the tag and mode values traveling in the query string get echoed straight back into href and hx-get attributes on every screen. Run them through UrlEncode before rebuilding any URL from them, or a crafted query string could break out of the attribute and inject markup.

Setting up the game endpoint's list field

The setup screen's List field uses Rock's built-in {[ dropdown ]} shortcode rather than a hand-rolled <select>, with the options built dynamically from the tag query above:

{[ dropdown label:'List' name:'tag' longlistenabled:'true' isrequired:'true' ]}
  [[ item value:'' text:'-- Choose a List --' ]][[ enditem ]]
  {% for t in tags %}
  [[ item value:'{{ t.Id }}' text:'{{ t.Name }}' ]][[ enditem ]]
  {% endfor %}
{[ enddropdown ]}

That shortcode's own markup carries a small inline script that initializes the enhanced, searchable dropdown, and it re-runs every time the shortcode's output is generated, including on an HTMX swap. That is what lets it work reliably even though this screen is delivered as a swapped fragment rather than a fresh page load.

How it's deployed

This runs as a Lava Application: one Lava Application with two LavaEndpoints (setup and game) plus a host page using the Lava Application Content Block. One trade-off worth knowing up front: a LavaEndpoint's own URL is a bare API path with no site chrome, so the address bar never updates as you play. A refresh mid-game always returns to the setup screen rather than resuming where you left off.

Optional: the "more about this person" panel

Both endpoints support a collapsed-by-default panel, shown after a card is answered or revealed, with campus, connection status, how long the person has been tagged with the list being played, household members, and up to three other non-family groups. It is controlled by a checkbox on the setup screen, threaded through the game the same way the score-tracking checkbox is. The excluded group type (Family, by default) is a single hardcoded GroupType GUID near the bottom of the endpoint file, easy to change if a church wants a different exclusion.

Validation checklist

Work through each of these before considering the game ready for real use:

  • ✓ A correct guess scores and advances to the next person.
  • ✓ An incorrect guess does not score and gives clear feedback.
  • ✓ People without a photo are never shown.
  • ✓ A page reload does not break the game or double-count the score.
  • ✓ The block only shows people the current user is permitted to see.
  • ✓ HTMX swaps update only the intended region, with no full page reload.
  • ✓ A tag with no eligible people shows a clear message instead of erroring.

Where this technique goes next

Here is the good part: nothing about this pattern is specific to a guessing game. A Lava block that pulls a random or filtered record, renders a fragment, and swaps itself with HTMX on every interaction works just as well for a quiz, a flash-card review, a rotating prayer request, a serving-team check-in, anything that needs partial-page interactivity without a JavaScript framework. The game was just the approachable place to start. The technique is yours now. We would love to see what you build with it.

Built with love for every church trying to make sure nobody stays a stranger. ❤️