Web Funnel Reports
This folder holds the traffic funnel for jodapp.com. The funnel shows how many
people arrive, how many look at a job, and how many apply.
This page is the definition. It says which events make up the funnel, which filters to apply, and which numbers you may trust. Dated pages next to this one hold each actual reading.
Why this page exists
Two people can pull "the funnel" from GA4 and get very different numbers. The reason is that the same GA4 property serves two different websites, and several event names do not mean what they sound like. This page fixes one definition so every report matches.
For the allowed values of every event parameter — enums, formats, and the rules about money — see the GA4 event parameter reference.
Vocabulary
Read this first. The rest of the page uses these words exactly.
- A GA4 property is one analytics account. Ours is
properties/515821279, namedjodapp.prod.web. - A hostname is the domain that sent the event. One property receives events from more than one hostname.
- An event is one action the website reports to GA4, for example
view_item. - A key event is an event we treat as success. For us that is
generate_lead. Careers::Jobis a normal job. The worker applies onjodapp.com.Gig::Jobis a shift-based job. The worker applies ongig-partners.jodapp.com.activeUsersis the GA4 user count used inside funnel reports.itemsViewedcounts job detail views. It counts events, not people.
The two hostnames
One GA4 property receives events from two separate websites.
| Hostname | What it is | Codebase | Where a worker applies |
|---|---|---|---|
jodapp.com | Main job board | jodapp-web (React Router v7) | Careers jobs apply here |
gig-partners.jodapp.com | Gig partner site | A separate repo | Gig jobs apply here |
Every funnel query must filter on hostName. If you do not, you mix two
products and the numbers mean nothing.
The filters
Apply both of these to every step of the funnel.
| Filter | Value | Why |
|---|---|---|
country | Singapore | Singapore is the market we sell in |
hostName | jodapp.com | Keeps gig-partners out |
In a funnel report you cannot set one filter for the whole report. You must repeat both filters inside every step. See the query recipe below.
Two things to say out loud whenever you use these filters:
hostNameis an exact match. It dropswww.jodapp.com,jobs.jodapp.comandcareers.jodapp.com. Those are worth about 1,068 sessions over 365 days. Small, but state it.- The Singapore filter is not most of the traffic. It removes about 46% of
jodapp.comsessions and about 80% of careers applications. Always report the unfiltered country split beside the Singapore number, so nobody reads a Singapore figure as a whole-site figure.
What each event really means
Check this table before you name an event in a report. Several events do not mean what the name suggests.
| Event | What it really means | Safe to call a conversion? |
|---|---|---|
session_start | A visit began | No |
view_item_list | A list of job cards scrolled into view | No |
select_item | Someone clicked a job card in a list | No |
view_item | A job detail page loaded | No |
view_item_jodapp | Copy of view_item, made for Google Ads | No |
generate_lead | A careers application was accepted by the API | Yes |
form_start | Someone focused any form field | No |
form_submit | Someone submitted any form | No |
The form_submit trap
form_submit is not an application. It is fired by GA4 enhanced measurement on
every form on the site. On jodapp.com over 90 days it splits like this:
/jobs— 972 events. This is the search and filter box./— 283 events. This is the homepage search box./login— 101 events./sign-up— 73 events./talent/setup— 69 events.
If you report form_submit as applications you overstate conversions by about
36 times (1,637 form_submit against 45 generate_lead). Do not do it.
The funnel
Use this shape. It has three steps.
| Step | Event | What it tells you |
|---|---|---|
| 1. Landed | session_start | Top of funnel |
| 2. Viewed a job | view_item | Real interest in one job |
| 3. Applied | generate_lead | A careers application |
Why the funnel skips the job list
An earlier version put view_item_list between step 1 and step 2. That version
undercounts. Many people arrive on a job detail page straight from Google and
never see a list. A funnel report in GA4 is ordered, so those people are dropped
at the list step and never counted as job viewers.
Measure the job list as a separate discovery funnel instead:
view_item_list— saw a list of jobsselect_item— clicked a cardview_item— the detail page loaded
The query recipe
Both filters must be repeated inside each step. This is the working shape.
{
"name": "2. Viewed a job",
"filter_expression": {
"and_group": {
"expressions": [
{ "funnel_event_filter": { "event_name": "view_item" } },
{ "funnel_field_filter": {
"field_name": "country",
"string_filter": { "value": "Singapore" } } },
{ "funnel_field_filter": {
"field_name": "hostName",
"string_filter": { "value": "jodapp.com" } } }
]
}
}
}
Run it with mcp__analytics__run_funnel_report against property 515821279.
Two limits of the tool:
- The funnel is always closed. Everyone must enter at step 1.
funnel_next_actionaccepts alimitof 5 or less. Larger values fail.
Splitting gig from careers
view_item on jodapp.com covers both job types. generate_lead on
jodapp.com covers careers only. If you compare them directly you understate
the careers conversion rate by about three times.
Split them with the standard item dimension itemCategory. It holds
Careers::Job or Gig::Job.
Item dimensions only work with item metrics. Use itemsViewed,
itemsViewedInList and itemsClickedInList. Adding eventCount returns a
400 error.
Always split by itemListName too
This rule exists because we already got it wrong once.
Comparing click rates for gig against careers across the whole site showed
careers at 3.09% and gig at 7.69%. That looked like a careers card quality
problem. It was not. Splitting by itemListName showed:
| Surface | Job type | Shown | Clicked | Rate |
|---|---|---|---|---|
| Search results | Gig::Job | 138,070 | 11,337 | 8.21% |
| Search results | Careers::Job | 19,972 | 1,539 | 7.71% |
| Home — gig module | Gig::Job | 10,260 | 372 | 3.63% |
| Home — careers module | Careers::Job | 40,275 | 323 | 0.80% |
| McDonald's page | Gig::Job | 5,710 | 136 | 2.38% |
On the same surface the two job types perform almost the same. The site-wide gap came from the mix: two thirds of careers card views come from the homepage module, which converts badly, while nine tenths of gig card views come from search results, which converts well.
Never quote a site-wide click rate by job type. Always split by surface first. A total that mixes surfaces with very different rates will point you at the wrong fix.
Known blind spots
State these in every report. They change what the numbers mean.
1. The "clicked Apply" step is missing
jodapp-web sends an event called job_apply_start when someone clicks Apply.
It was added on 2026-01-15 and it fires on both job types. GA4 has received
zero of these events across the whole life of the property. The property
started collecting on 9 December 2025, so that is every day the event could
possibly have been recorded.
Four other events behave the same way — login, sign_up, search and
share. All five exist in the app code. None reach GA4.
The cause is confirmed. Container GTM-NKWB9VWN was inspected through the
Tag Manager API on 2026-07-30. It holds only 6 tags and 3 triggers.
One generic tag, GA4 Event - Ecommerce, forwards our app events. It sends
eventName: {{Event}}, so it passes through whatever name is in the data layer.
It would forward job_apply_start without any change.
The blocker is the trigger that fires it. Trigger GA4 - Universal Ecommerce Items only matches this list of names:
^(view_item|view_item_list|select_item|add_to_cart|begin_checkout|purchase|generate_lead)$
The five silent events are not in that list, so the trigger never fires for them.
Note what else that list shows. add_to_cart is still allowed, but the app
stopped sending it on 2026-01-15 when commit 44c99eac replaced it with
job_apply_start. The app was changed and the GTM trigger was not. So the
event was renamed on one side of the boundary only.
The fix is small: add the event names to that trigger's pattern. Give the four
non-ecommerce events (login, sign_up, search, share) their own tag with
ecommerce sending turned off.
Two things this ruled out:
- Tags built but never published. The draft workspace is identical to the live version. Nothing is waiting unpublished.
- Wrong GA4 property. The container picks the property from the page
hostname.
jodapp.comcorrectly maps to the production propertyG-LNFY0YRZD8. Other hostnames go to the QA property.
One partial exception: search is already covered in a weaker form by GA4
enhanced measurement, which sends view_search_results. That is 654 events over
90 days in Singapore. It has no search_term, so it cannot tell you what people
looked for.
The effect on the funnel is large. Without job_apply_start we cannot tell
these two cases apart:
- People look at a job and never click Apply.
- People click Apply and then give up at the login or profile step.
These need opposite fixes. One is a demand problem. The other is a signup problem.
2. Gig applications are not on this site
Roughly 70% of job detail views on jodapp.com are gig jobs. A gig worker is
sent to gig-partners.jodapp.com to apply. So a funnel filtered to
jodapp.com can never show a gig conversion.
Report gig and careers separately. Never divide all generate_lead events by
all view_item events.
3. The apply flow has two hidden gates
In jodapp-web/app/routes/jobs/job-detail-page.jsx, a careers application
passes two checks after the user clicks Apply:
- Not logged in — the user is sent to
/login. - Career profile not finished — the user is sent to
/talent/setup.
Neither gate sends an event. People lost at these two gates are invisible.
4. The ecommerce object is never cleared — but no leak was found
The app never pushes ecommerce: null between events. GA4 merges the data
layer, so in theory items from an earlier view_item_list can leak into a later
event.
We checked, and the data is clean:
itemsViewedis 22,066 andview_itemis 22,066 events. Exactly one item per event.itemsClickedInListis 13,707 andselect_itemis 13,707 events. Exactly one item per event.
So item splits are trustworthy today. Re-check this if the app starts sending multi-item events.
5. GA4 funnels are closed and ordered
Everyone must enter at step 1 and do the steps in order. Anyone already mid-
journey is dropped. Over 90 days this excluded 22.7% of session_start users,
25.7% of view_item users, 31.9% of select_item users, and 37.0% of
generate_lead users.
Always publish this comparison next to the funnel. Read every funnel rate as "among people who started here in this window".
6. Measure the gig handoff with listing_job_id, not session source
The two sites are joined by the GA4 cross-domain linker, so a worker moving from
jodapp.com to gig-partners.jodapp.com keeps the original session and the
original source. A low count of jodapp.com as a source is what a working
linker looks like. Do not read it as a tracking failure.
The utm_source=jodapp.com tag is only on login links. It does not measure
job handoffs.
Instead, count gig job pages on gig-partners whose URL carries listing_job_id.
jodapp.com adds that parameter to every gig apply redirect. Over 90 days,
6,069 of 6,179 gig job views on gig-partners carried it, which is 98.2%.
Rules for anyone writing a funnel report
- Always filter by
hostName. Never report a number that mixes both sites. - Always check an event with a
pagePathquery before you name it. - Never call
form_submita conversion. - Always split
Careers::JobfromGig::Jobbefore quoting a conversion rate. - Always split by
itemListNamebefore quoting a click rate. A site-wide rate hides which surface is failing. - Always publish the country split beside any Singapore number. Singapore is about 20% of careers applications, not most of them.
- Always publish how many users the closed funnel excluded.
- Never draw a conclusion from a segment with fewer than about 30 conversions. The whole Singapore careers funnel has 27. Device and channel cuts of it cannot carry a decision.
- Always say whether a rate is events divided by events, or people divided by people. They are different numbers.
- Always repeat the blind spots above.