Notes › Tech Notes › Why one ad campaign shows up as two rows: UTM parameters, case and encoding

Why one ad campaign shows up as two rows: UTM parameters, case and encoding

By the second week of a campaign I started to distrust the rows in the report. The same batch of assets ran through a single email channel, yet the back office split the source into two rows, one spelled Newsletter and one newsletter, and only the sum of the two matched the document. Another problem had been planted earlier: a campaign name in a link the ops team sent out contained a space, and after percent-encoding the string was longer than the version in the document, so nobody could say which one was authoritative. Neither was a matter of forgetting parameter names; I took that batch of links apart one by one.

Five parameters, one dimension each

The five standard keys each answer one question: utm_source is who sent the traffic, utm_medium is what kind of channel it came through, utm_campaign is which campaign it belongs to, utm_term is which paid keyword, and utm_content is which creative in the same slot. The dimensions multiply, which is where report row counts come from.

  • 3 sources, 2 mediums, 4 campaigns and 2 creatives multiply to 48 links.
  • Leave the creative slot out and count only to campaign level: 24 links.
  • Spell one source in two cases and under case-sensitive aggregation each gets its own row, turning 48 into 96; that depends on platform configuration, and not every analytics tool behaves the same.
  • Values containing Chinese characters get longer still: the 5 characters of 拼音 转换 become 39 characters after encoding.
  • 双十一 大促 is 6 characters, 16 bytes in UTF-8, and 48 characters after encoding, with the single space taking the 3 characters of %20 on its own.

How the sample links are built, counted in characters

The links below are samples from my document, measured in characters of the URL string itself; ? belongs to no section, so the first two columns do not add up to the full length.

SampleLink partParameter partFull lengthParameter status
Landing page already has from=push, three utm parameters added4478123from keeps its value, all three utm are new
Old link already has utm_source=weibo and two more, campaign name replaced with nov-2026-beta4467112Three utm values overwritten, id=88 keeps its value
Landing page with a fragment and a Chinese query term, four parameters added3498133Query term keeps its value, four added, fragment still last
Landing page with port :8080, three parameters added4968118All three are new
Same landing page taken to four parameters, creative slot set to banner-top4488133All four are new

The campaign-name row is the most useful: a parameter of the same name keeps only one copy, so after replacing it with nov-2026-beta the link does not carry two utm_campaign groups, and which one a tool reads is not our decision; non-utm parameters such as id and from travel through unchanged.

Assembling 48 links by hand is not realistic, and what I wanted to lock down was the naming convention. UTM Link Builder lays the five parameters out as fields; source and medium use channel presets that fill in a single lowercase hyphenated name, percent-encoding is automatic, and each parameter is labelled as added, overwritten or kept. Batch variants take one value per line, so links in the same batch can be compared line by line, and assembly happens locally in the browser with no upload.

Encoding inflates length, not meaning

utm_content=拼音 转换
utm_content=%E6%8B%BC%E9%9F%B3%20%E8%BD%AC%E6%8D%A2
Value as typedForm in the linkLength change
拼音 转换%E6%8B%BC%E9%9F%B3%20%E8%BD%AC%E6%8D%A25 characters to 39 characters
双十一大促Each byte expands to 3 %XX characters15 bytes to 45 characters
双十一 大促%E5%8F%8C%E5%8D%81%E4%B8%80%20%E5%A4%A7%E4%BF%836 characters and 16 bytes to 48 characters
A single space%203 characters

Filling only utm_content with the Chinese value that contains a space makes the whole link 96 characters with 51 in the parameter part: the meaning has not moved, but the literal length has.

The plus sign is another convention problem. A plus means a space only inside form encoding, application/x-www-form-urlencoded; written in a URL path or query string it is a literal plus, and mixing the two conventions produces the classic plus-turns-into-space incident. My rule is no spaces in values, use hyphens: write banner-top rather than banner top. If all you want is to verify one encoded result, URL Encoder and Decoder is enough.

Order does not change meaning, but it changes the string

Everything after ? is a set of key-value pairs, so writing utm_campaign before utm_source still lands in the analytics tool as the same set:

?utm_source=weixin&utm_medium=social&utm_campaign=oct-2026-release&utm_content=banner-top
?utm_campaign=oct-2026-release&utm_content=banner-top&utm_source=weixin&utm_medium=social

But they are two different URL strings, and a deduplication rule based on the URL string treats them as two records. I now fix the order: when the landing page has no parameters of its own, sort as source, medium, campaign, term, content; in samples that already carry parameters, those kept or overwritten stay in place and new ones are appended.

Q: Should utm_term be filled in?

Only when you can name the paid keyword. Search engines stopped passing specific organic search terms long ago, so forcing a term onto a non-paid channel just adds a dimension with nothing to compare against. For this batch the medium is email and the term field was left entirely empty; none of those sample links contains utm_term either.

Q: Who unifies the case?

Nobody does it for us; the values are what we write. Under case-sensitive aggregation Weibo and weibo are two rows, and the exact behaviour depends on platform configuration. I prepare for the case-sensitive scenario and keep only lowercase hyphenated names in the value table.

Q: What about the order of # and ?

In that sample with a fragment, #top still comes last and the parameters sit between ? and #; the fragment does not enter the query string and takes no part in distinguishing these parameters.

Q: What is the difference between assembling by hand and generating?

Hand assembly leaves you with something that is correct once; generation leaves you with something checkable: five keys filled at once, parameters of the same name marked as overwritten or kept, and lengths recorded separately. To understand how a link is cut into sections and where query encoding happens, URL structure and query encoding explains it in more detail than I can here.

Back to those two rows. The source was split not because someone wrote the parameter name wrong, but because the same value had two literal spellings and the report separated them literally; the campaign name with a space did not match the document because the encoding was wrong, it was always going to be longer than the original. My trade-off: define the naming table first, all lowercase, hyphen separated, the same name across every channel for one campaign such as oct-2026-release; leave encoding to the last step and do it once; fix a single parameter order. Then 48 links handed to several people should produce only the rows that belong in the report, and if it does become 96 rows I know which field to check first for a case mismatch instead of doubting the analytics.

📎 Related articles

PNG compressed three times only lost 426 bytes: the real boundary of lossless compression 2026-10-22 How Many Times Bigger a PNG Gets After Converting From WebP: When It Is Unavoidable and How to Keep the Growth Down 2026-10-22 Three Reasons the Small Text Smudges When a Screenshot Is Shrunk to a Quarter 2026-10-22 Cropped to the Same 300x300, Why Does the Avatar Always Sit Half a Cell Low-Right 2026-10-22 Forecasting 18 months at 1.7 times a month: why the script and the sheet give two numbers 2026-10-22