Skip to main content
Terminus
Contents
The Letter and the LinkChapter 4 · ~11 min read

Taxonomy Types

Flat, Structured, Key-Value and Opaque ID: what each packs into one slot, what it takes to read it back, what each survives, and what each asks of your team.

On this page
The chapter in 5 min, as a video.Next video: Chapter 5 · Who Writes the Link →

Your reports are clean. Then your boss asks: “Which campaign sold the most sneakers in Europe?”

You look at your campaign values. Maybe they carry the product, the way Stride’s carry runners. None of them carries a region, because nobody ever wrote one down. There’s no Europe to pull out.

The usual next thought is that you need a fancier naming structure, and that a team your size should be on one. Teams that act on it climb to structures they can’t keep clean, or stay with names nobody can split, and both choices are made for the wrong reason.

A fancier structure isn’t better data. It’s a different deal with whatever reads your names back, and the size of your team was never part of the deal.

Every structure is a promise to its parser

Chapter 2 gave you five slots, and Chapter 3 put three facts into one of them: spring_sale-runners-2025_03 carries the initiative, the product and the launch month in the campaign slot. Packing is the easy half. The other half is getting them back out, so a report can group by product or filter by month.

Something has to split the value back into its parts and read each part for what it is. In a spreadsheet it’s one formula: split the campaign column on the hyphen, and spring_sale-runners-2025_03 becomes three columns (initiative, product, month), ready to filter. In a data warehouse, where all your data lands, it’s one line of a query. In a report it’s a filter that matches the middle piece. Chapter 3 showed what makes a split possible: hyphens between segments, underscores inside one, which is why summer-sale-shoes fell apart and summer_sale-shoes didn’t. That step is parsing.

Whatever does it is your parser, and every structure is a promise to it.

Now try the opposite:

spring_sale_2025_facebook_social_video_ad_a

Everything is in there, and none of it can come back out. No separator says where the campaign ends and the platform begins. Nothing can parse it.

A structure is the agreement about how values get packed so they can be parsed. There are four.

Four ways to pack a slot

Flat. One dimension per value: spring_sale, facebook, paid_social. Nothing to parse, which is its strength and its limit. Sources and mediums stay Flat in almost every taxonomy, because each of those slots carries one fact (a source may add one detail segment, like partner-acme). The moment a slot needs to carry two, Flat has nowhere to put the second.

Structured. Segments in a fixed order, joined by hyphens. It’s built by concatenation: you take the values for each field and join them in order. Stride’s campaign pattern is Structured already:

stride.com/spring-sale?utm_campaign=spring_sale-runners-2025_03

utm_campaign, one segment per hyphen

  1. spring_saleinitiative
  2. runnersproduct
  3. 2025_03launch month

Position carries the meaning: the second piece is the product because it’s second. Choosing which segments go in, and in what order, is part of the deal:

  • As few segments as your reports need. us-summer_sale-shoes-awareness-2025_q2 carries five (region, initiative, product, objective, quarter), and some teams run ten. Each one adds a choice on every link, a position that can slip, and length against the ad platforms’ name limits (Chapter 6). Long names hold up when a builder asks for each segment separately (a dropdown for region, another for product) and joins them in order, so nobody types the whole name (Chapter 9). Typed by hand, a long name is where segments get skipped or swapped.
  • Every segment on every name. When a dimension doesn’t apply, give it a value that says so (na), or keep its empty place between two hyphens. na is easier to read and to filter on; an empty place keeps the name shorter and is easier to miss. Either way the gap stays open: close it up, and us-summer_sale-shoes-awareness-2025_q2 without its region reads summer_sale as the region.
  • Never source or medium: their own slots already carry them.
  • Put the segment you filter by most first.

Key-Value. Each segment labels itself with a key and a colon:

stride.com/spring-sale?utm_campaign=init:spring_sale-prd:runners-launch:2025_03

utm_campaign, one segment per hyphen

  1. init:spring_saleinitiative
  2. prd:runnersproduct
  3. launch:2025_03launch month

The order no longer matters, and a name only carries the keys it needs. The colon has one job in this book, and this is it. The cost shows up at the other end: every tool that reads these names needs someone who can split the pairs and look each value up by its key.

Opaque ID. The link carries one meaningless identifier, cid_4d71e09a, and every dimension lives in a lookup table on your side, the same two-column idea as Chapter 1’s lookup, grown into a table:

IDInitiativeProductLaunch
cid_4d71e09aspring_salerunners2025_03

In the Introduction’s terms, the link carries a pointer to the letter instead of the letter. The pointer buys two things a name can’t. Fix a wrong value in the table and every click that ever carried the ID is corrected, with no link retagged. And the table holds as many columns as you like.

The day the region arrives

Now put the four to the same test. Suppose Stride had written its letter three ways from day one:

StructureStride’s spring sale
Structuredspring_sale-runners-2025_03
Key-Valueinit:spring_sale-prd:runners-launch:2025_03
Opaque IDcid_4d71e09a, with the row above

Then Stride starts selling in Europe, and every name needs a region, in front, where it’s filtered most. New names get one. Before you open the answer: what does a parser built for the new names read from the names already recorded, in each structure?

Show what happens
StructureNew namesAn older name, read by the new parser
Structuredus-spring_sale-runners-2025_03 (the spring sale, had it been written the new way)region spring_sale, initiative runners, product 2025_03, launch empty
Key-Valuegain a geo:us paireverything it had, with no region
Opaque IDrows gain a region columnthe table fills in the region; no link changes

Structured pays at the parser: every older name now reads one segment off, because position was the meaning. Key-Value pays nothing for the old names, which simply have no region. Opaque ID pays in the table: someone fills in the region for every older ID. That doesn’t contradict Chapter 1’s rule that nothing can be added to clicks already made: the clicks are exactly what they were; only the row behind the ID is new.

And the Structured cost comes from where the region went:

  • Region last: older names parse with the region empty.
  • Region first, where it’s filtered most: every older value shifts.

There’s a second catch in the Structured row. After the change, the parser sees two kinds of names, three segments and four, and it has to be told which is which. Running both formats side by side until the old ones fade is its own piece of work, and Chapter 10 is where it gets done.

Every structure can take the change. Each one pays for it in a different place.

Choose by capability, not headcount

Each structure sends its bill to your team: what it can build, and what it can keep running.

  1. FlatNo parsingone dimension per value
  2. Structuredthe common defaultA spreadsheet splitsplit on the hyphen; a non-technical team can do it
  3. Key-ValueA key-aware parsersome technical skill, in every tool that reads the names
  4. Opaque IDA warehouse and a processone source of truth, every system joined to the table, a table kept complete
Each rung asks for more. Higher rungs cost more to run; they aren't better.

Opaque ID’s rung deserves spelling out, because it asks the most. Every report has to rebuild its dimensions by joining the ID to the table, so it needs three things:

  1. One source of truth. All your data lands in one place first, usually a data warehouse: the one copy everyone reads from.
  2. Every system integrated. Analytics, the CRM, the lead forms and the ad platforms all have to join to it.
  3. A table kept complete before any link ships, because an ID with no row is a click with no meaning.

So when a team has to pick, the default runs in one direction:

  1. Concatenation. A good choice, and the most common one.
  2. Key-Value, next. For names that must survive new dimensions, once something can read keys.
  3. Opaque ID, last. Only when you know every system it will affect and can join each one to the table.

Most teams don’t need Opaque ID. It’s elegant, and the operational overhead kills adoption.

Size isn’t the test either way. A large company can run on concatenation, and most likely needs it. A small one shouldn’t take on Opaque ID, however tidy it looks.

The four side by side

FlatStructuredKey-ValueOpaque ID
What one slot carriesone dimensionseveral, in a fixed orderseveral, each labelledan ID; the dimensions live in a table
Reading it backnothing to parsea split on the hyphena parser that reads keysa join to the lookup table
A segment added or skippedno room to add oneshifts every value after itnothing breaksadd a column; nothing to skip
What it asksnothinga split on the hyphentechnical skill in every toola source of truth, integrations, a process

Read the bottom row first. The top three describe your names; the last describes your team, and it’s the row that decides.

Both teams chose by the same test. Stride’s names only need to survive a second region it can see coming; the home goods brand needed three dimensions at once, more on the way, and analysts who could read keys. Neither chose by counting people.

Your situation

Read your structure off three questions:

  • How often do your dimensions change? Rarely, and concatenation holds. Often, and Key-Value is the one where nothing breaks.
  • What parses your names today? A person or a spreadsheet split: concatenation. A query that reads keys: Key-Value is open to you.
  • Can your team keep a lookup table complete, with every system joined to one source of truth? Yes: Opaque ID is on the table. Not yet: it isn’t.

What changes

The decisionWhen structure is a status symbolWhen structure is a parsing deal
Which structurethe most advanced onethe one your names must survive and your parsers can read
What decides ithow many people make linkstechnical and process capability, and what each choice costs to run
Adding a dimensiona new segment wherever it fitsa new segment shifts every older value after it, so decide where it goes and put the most-filtered first, or use keys
Opaque IDflexibility, so why notonly when every system it touches is known and can join to the table

Where the rule lives

Every structure meets its real test when someone types a name. Its rules sit in one of two places:

Beside the path

Written in a planning doc, the structure is a rule people remember segment by segment, and one skipped segment shifts everything after it.

Inside the path

Built into the builder, each segment is its own field, and a skipped one can’t ship.

Check yourself

A campaign is typed by hand under the pattern as spring_sale-2025_03, with the product skipped. What does a parser read?

Show answer

Initiative spring_sale, product 2025_03, and an empty launch month. Every value after the gap shifts one place, because position carries the meaning.

A 200-person team's only parser is a spreadsheet that splits on hyphens. Key-Value, Opaque ID or concatenation?

Show answer

Concatenation: it’s what they can parse. Size isn’t the test. Key-Value only once something can read keys, and Opaque ID only once every system joins to one table.

A colleague proposes Opaque ID because it's the most flexible, and your data lives in an analytics tool and a CRM that don't share a warehouse. What do you weigh?

Show answer

What it asks: one source of truth, every system joined to the table, and a process that keeps the table complete before any link ships. Without those, concatenation or Key-Value will serve you better.

In one line

Pick a structure by what your names must survive and what can parse them, never by headcount: concatenation first, Key-Value next, Opaque ID only when every system it touches is known.

Before the next chapter

Write down what parses your campaign names today (a person, a spreadsheet, a warehouse query) and one change it couldn't survive. That's the limit of your structure.

Next · Chapter 5 · Who Writes the LinkYou have the letter and its structure. How does it get onto every link you ship, channel by channel?