Demo

Staff Directory

WordPress plugin — national trade association, 2026

A staff page the comms team can update without me

About fifty employees, typed by hand into one page. It went out of date every time someone joined or left, and every fix came back to a developer. I rebuilt it as a plugin: a directory you can search, a form for adding people that only administrators can see, and a spreadsheet import that tells you what it is going to do before it does it.

  • PHP 7.4+
  • WordPress 6
  • Custom post type
  • Vanilla JS, no framework
  • CSV and XLSX parsing
  • WCAG 2.2 AA

Try it

This is the real interface running on invented people. Search it, add someone, or paste a spreadsheet and watch what the importer would do.

Our team
ANAdaora Nwankwo

Director, Regulatory Affairs

THTomás Herrera

Vice President, Communications

PRPriya Raman

Senior Manager, Events

DADevon Ackah

Network Administrator

Directory

Search instead of scrolling

Each person is a record in the database, not a line of HTML. The page lists them by surname and filters as you type.

Nobody had a headshot on file, so the plugin draws their initials instead.

How it was built

A custom post type with real fields, rendered by a shortcode that drops into any page. Filtering runs in the browser over records that are already loaded, so there is no request to wait for and nothing to install alongside it.

Our team
LK
Lena Karlsson

President and Chief Executive Officer

Leads the association’s advocacy work, member programs and annual conference.

Full bioMeet the team

Leadership

One person sits above the rest

The chief executive gets their own banner, with space for a photo, a short biography and a link to a longer one. Everyone else stays in the grid.

It is a checkbox on the record rather than a name in the code. When the job changes hands, nobody has to call me.

How it was built

The query that builds the grid also lifts the featured person out of it, so the two blocks cannot drift apart. Edit a job title once and it updates in both.

Import roster
12 to add, 3 to update, 1 needs a look
RowNameAction
2Maren EllisdóttirAdd
3Tomás HerreraUpdate
4Ruth OkonkwoAdd
5(no name given)Check

Nothing is saved until you confirm this list.

Bulk import

Fifty hires shouldn’t mean fifty forms

HR keeps the roster in a spreadsheet already. Upload it, match the columns once, and the plugin shows what will change.

Rows missing something required are flagged and skipped, rather than quietly importing half a person.

How it was built

CSV is read natively. XLSX files are zipped XML, so the plugin unzips and reads them with what PHP already has, avoiding a ten megabyte library. People are matched on email first, then on first and last name, so uploading the same file twice updates instead of duplicating.

Add a staff member
Maren
Ellisdóttir
Senior Director, State Affairs
m.ellisdottir@example.org
Active
Administrators only. Everyone else sees nothing here. Not a warning, not an empty box, nothing.
Add staff member

Permissions

The form is on the page, and most people never see it

Adding someone happens on the site itself. The same form edits and archives, so a departure takes about ten seconds to handle.

Archiving keeps the record and drops the person from the public list. Nothing is deleted by accident.

How it was built

The shortcode returns nothing unless you hold a specific capability, which administrators get when the plugin activates. Because it is a capability and not a role, staff upkeep can be handed to an HR editor later without handing over the site.

Sorted by surname
  • Ana (Reyes) Duarteduarte ana
  • Cormac MacAllistermacallister cormac
  • Maeve O’Brienobrien maeve
  • Liesel van Dijkvan dijk liesel

The grey key is what the database sorts on. Readers never see it.

Sorting

Names break simple alphabetising

Split a full name at the last space and it works until someone has a maiden name in brackets, an apostrophe, or a surname beginning with “van”. Then the order goes quietly wrong and nobody notices for months.

So first and last names are stored separately, and each record carries a sort key with the punctuation taken out.

How it was built

The key is built when a record is saved, not when the page loads, so ordering costs one indexed lookup instead of sorting in PHP on every visit. Anyone whose name still lands in the wrong place gets an override field, which keeps an edge case from becoming a support ticket.

    Screen 1 of 5

    Let's build something.

    Have a project, a role, or an idea worth prototyping? I'd love to hear about it.