Custom Conversion Value Rules in Google Ads: The JavaScript Playbook
Google Ads gives you three native conversion value rule dimensions in the UI: geographic location, device type, and audience segment. They are genuinely useful. They are also genuinely limited.
The reality of most businesses is that the signals that actually predict lead quality or purchase intent go far beyond where someone is clicking from or whether they are on a phone. The time of day a form gets submitted. Whether someone found you through a branded search or a generic one. How many pages they visited before converting. Whether the URL contains a quote parameter that tells you the job size.
None of those live in the native value rule UI. All of them can be built in JavaScript.
This article gives you working, copy-paste JavaScript code for custom conversion value rules built on signals the Google Ads platform does not natively offer. Each snippet dynamically computes a conversion value before passing it into the gtag conversion event, which means Smart Bidding receives a more accurate signal about what each conversion is actually worth to your business.
Important framing: these scripts do not replace Google's native value rules. They work by computing a dynamic value in JavaScript and passing that value directly into your gtag conversion call. The result is the same , Smart Bidding gets a differentiated value signal, but the mechanism is client-side logic rather than a platform-level rule.
How This Works: The Core Mechanic
A standard Google Ads conversion tag fires a fixed value:
// STANDARD FIXED-VALUE CONVERSION TAG
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': 50.0,
'currency': 'USD'
});
Every conversion fires at 50. Smart Bidding treats a midnight form submission from someone who found you via a generic keyword on their first visit exactly the same as a 2pm submission from a returning user who came from a branded search and visited your pricing page first.
That is an enormous amount of signal being discarded.
The approach here replaces that fixed value with a dynamically computed one:
A standard Google Ads conversion tag fires a fixed value:
// DYNAMIC VALUE PATTERN
var baseValue = 50.0;
var multiplier = computeMultiplier(); // your custom logic
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': baseValue * multiplier,
'currency': 'USD'
});
The computeMultiplier() function is where all of the logic below lives. You can stack multiple signals into a single multiplier, or keep them separate and pick one per conversion action. Both approaches work.
Everything below is written as a self-contained snippet you can drop into a Custom HTML tag in GTM or directly into a page script. Each one fires the conversion event itself, so do not stack these on top of an existing conversion tag for the same action.
Rule 1: Time of Day and Day of Week
When a conversion happens matters enormously in certain businesses. A service business that handles emergency call-outs values a 9pm Sunday form submission very differently from a 10am Tuesday one. A B2B company selling to office workers should weight weekday business-hour leads higher than weekend ones. A restaurant taking online reservations should value Friday and Saturday evening bookings above Monday lunches.
None of this is available in the native value rules UI. It is three lines of JavaScript.
The business logic
B2B / office-hours businesses: Weekday, business-hours conversions worth more. Weekend and late-night submissions worth less — longer sales cycle, lower intent signal.
Emergency services: Evenings and weekends worth significantly more. That is when genuine urgency happens.
E-commerce: Peak shopping hours (evenings, weekends) lead magnets might be worth more if your catalogue is impulse-friendly. Adjust to match your actual purchase data.
// GTM CUSTOM HTML TAG — TIME AND DAY VALUE RULE
<script>
(function() {
var baseValue = 50.0;
var now = new Date();
var hour = now.getHours(); // 0-23 local browser time
var day = now.getDay(); // 0 = Sunday, 6 = Saturday
var isWeekend = (day === 0 || day === 6);
var isBusinessHours = (hour >= 9 && hour < 18);
var isEvening = (hour >= 18 || hour < 6);
var multiplier = 1.0;
// --- Adjust these multipliers to match your business model ---
// B2B / office-hours model: weight business-hours weekday leads higher
if (!isWeekend && isBusinessHours) {
multiplier = 1.3; // +30% for weekday business-hours lead
} else if (isWeekend) {
multiplier = 0.7; // -30% for weekend lead
} else if (isEvening) {
multiplier = 0.85; // -15% for late-night submission
}
// Emergency services model (swap in place of the block above):
// if (isEvening || isWeekend) { multiplier = 1.5; }
// else { multiplier = 1.0; }
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY', // replace with your ID
'value': baseValue * multiplier,
'currency': 'USD'
});
})();
</script>
The outcome of it should yield data that could easily be fed back to the machine for value-based bidding and optimisation without necessarily hard-configuring your settings or using 500 bid modifiers across varying parameters in the platform.
A note on time zones: new Date() uses the browser's local time, which is the user's time zone. For most use cases involving local service businesses, that is correct. If you are running national campaigns and your business operates in a single time zone, you may want to convert to a fixed offset instead. The adjustment is a one-liner: subtract or add the UTC offset in milliseconds before calling getHours().
Rule 2: Traffic Source and UTM Medium
Not all traffic converts with the same downstream quality, and Smart Bidding should know the difference.
A lead from a branded search ("your company name + service") has demonstrated prior brand awareness. They already knew who you are before they clicked. That is a warmer lead than someone who clicked on a generic keyword for the first time. A lead from a retargeting campaign has already visited your site. A lead from an affiliate or comparison site has been through a filtering layer before arriving. The UTM parameters in the landing URL carry all of this information. You just need to read them.
The business logic
utm_medium=cpc + branded: Highest multiplier. They knew you before they clicked.
utm_medium=cpc + generic: Baseline. Unknown intent level until they convert.
utm_medium=email: Already a contact. Potentially high downstream value depending on the list.
utm_medium=affiliate or referral: Often lower quality, higher volume. Reduce the value signal. Direct (no UTM): Ambiguous — could be type-in traffic or a stripped UTM. Treat as neutral baseline.
// GTM CUSTOM HTML TAG — UTM SOURCE VALUE RULE
<script>
(function() {
var baseValue = 50.0;
var multiplier = 1.0;
// Parse URL params
var params = new URLSearchParams(window.location.search);
var medium = (params.get('utm_medium') || '').toLowerCase();
var source = (params.get('utm_source') || '').toLowerCase();
var campaign = (params.get('utm_campaign') || '').toLowerCase();
// Store on first landing — UTMs disappear on page navigation
// Use sessionStorage so they persist across the session
if (medium) sessionStorage.setItem('cv_medium', medium);
if (source) sessionStorage.setItem('cv_source', source);
if (campaign) sessionStorage.setItem('cv_campaign', campaign);
// Read back from storage (handles conversions on non-landing pages)
medium = sessionStorage.getItem('cv_medium') || medium;
source = sessionStorage.getItem('cv_source') || source;
campaign = sessionStorage.getItem('cv_campaign') || campaign;
var isBranded = campaign.indexOf('brand') !== -1 ||
source.indexOf('brand') !== -1;
// --- Adjust multipliers to match your traffic quality data ---
if (medium === 'cpc' && isBranded) {
multiplier = 1.4; // Branded paid: high intent, knows the brand
} else if (medium === 'cpc') {
multiplier = 1.0; // Generic paid: baseline
} else if (medium === 'email') {
multiplier = 1.25; // Email: existing relationship
} else if (medium === 'organic') {
multiplier = 1.2; // Organic: high research intent
} else if (medium === 'affiliate' || medium === 'referral') {
multiplier = 0.75; // Affiliate/referral: often lower quality
} else if (medium === 'social') {
multiplier = 0.85; // Social: lower purchase intent average
}
// Direct / no UTM: stays at 1.0 (neutral)
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': baseValue * multiplier,
'currency': 'USD'
});
})();
</script>
The sessionStorage pattern matters here. UTM parameters only exist in the URL of the landing page. If your conversion fires on a thank-you page or a confirmation step several clicks later, window.location.search will be empty. Storing UTMs in sessionStorage on page load and reading them back at conversion time ensures the attribution follows the user through the session.
If you are using GTM, the cleanest implementation is a separate tag that captures UTMs into sessionStorage on all pages (trigger: Page View, all pages), and this tag only handles the conversion event.
Rule 3: Engagement Depth Before Conversion
Two people can submit the same lead form. One arrived, scrolled to the form, and submitted in ninety seconds. The other visited your pricing page, your about page, your case studies section, and spent seven minutes on the site before converting. Which one is more likely to close?
Which one should Smart Bidding value higher?
Pages visited and time on site are crude but effective proxies for purchase intent. They are also entirely readable from the browser without any server-side data.
The business logic:
High page depth (5+ pages) + long session: Research-mode buyer. Higher downstream close rate in most B2B and high-ticket contexts.
Medium depth (2-4 pages): Normal consideration. Baseline value.
Low depth (1 page) + very fast submission: Could be a bored form-filler or a competitor. Lower value signal.
Pricing or high-intent page visited: Strong buy signal regardless of total depth. Weight this separately.
// GTM CUSTOM HTML TAG — ENGAGEMENT DEPTH VALUE RULE
<script>
(function() {
var baseValue = 50.0;
var multiplier = 1.0;
// Track pages visited in this session
// This relies on a separate page-view counter tag firing on all pages.
// That tag does: sessionStorage.setItem('cv_pages',
// (parseInt(sessionStorage.getItem('cv_pages') || 0) + 1));
var pagesVisited = parseInt(sessionStorage.getItem('cv_pages') || 1);
// Time on site: store session start on first page load
// Separate tag on all pages: if (!sessionStorage.getItem('cv_start'))
// sessionStorage.setItem('cv_start', Date.now());
var sessionStart = parseInt(sessionStorage.getItem('cv_start') || Date.now());
var minutesOnSite = (Date.now() - sessionStart) / 60000;
// High-value pages visited (adjust these paths to match your site)
var highIntentPages = ['/pricing', '/get-a-quote', '/contact',
'/book', '/plans', '/request'];
var visitedHighIntent = sessionStorage.getItem('cv_high_intent') === 'true';
// Track this with: if (highIntentPages.some(p =>
// window.location.pathname.startsWith(p)))
// sessionStorage.setItem('cv_high_intent', 'true');
// --- Value logic ---
if (visitedHighIntent) {
multiplier += 0.3; // Visited pricing/quote page: +30%
}
if (pagesVisited >= 5 && minutesOnSite >= 5) {
multiplier += 0.2; // Deep engagement: +20%
} else if (pagesVisited >= 3) {
multiplier += 0.1; // Moderate engagement: +10%
} else if (pagesVisited === 1 && minutesOnSite < 1) {
multiplier -= 0.2; // Bounce-and-submit: -20%
}
multiplier = Math.max(multiplier, 0.5); // Floor: never below 50% of base
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': baseValue * multiplier,
'currency': 'USD'
});
})();
</script>
This snippet requires two supporting tags in GTM that run on all page views: one that increments the cv_pages counter in sessionStorage, and one that sets cv_start on the first page load. Both are simple Page View tags with a few lines of Custom HTML. The high-intent page tracker is a third tag that fires on specific URL paths.
The floor at the end (Math.max) is important. Without it, a combination of negative signals could send a near-zero value to Smart Bidding, which disrupts the algorithm's learning. A floor of 50% of base value keeps the signal range sensible.
Rule 4: Form Field Value Signals
This one is specific to lead generation businesses where the form itself contains information about the quality or size of the conversion.
A home improvement company asking about project type on their quote form. A B2B SaaS capturing company size on sign-up. A law firm asking about case type on an enquiry form. A clinic capturing the type of treatment requested. In every one of these cases, the answer to a form field is a direct proxy for the downstream value of that conversion — and you can read it before the conversion fires.
The business logic:
-You are reading the selected or entered value from the form field at the point of submission and using it to set the conversion value directly, rather than using a generic multiplier. This is the most accurate of all the approaches in this article because it uses actual business data rather than a behavioural proxy.
// GTM CUSTOM HTML TAG — FORM FIELD VALUE RULE (PROJECT TYPE EXAMPLE)
<script>
(function() {
var conversionValue = 50.0; // Default fallback
// Read the project type field
// Adjust the selector to match your form's actual field ID or name
var projectField = document.getElementById('project_type') ||
document.querySelector('[name="project_type"]');
if (projectField) {
var projectType = projectField.value.toLowerCase().trim();
// --- Map form values to conversion values ---
// Replace these with your actual form values and business economics
var valueMap = {
'kitchen renovation': 350,
'bathroom remodeling': 280,
'full home renovation': 600,
'painting': 80,
'general inquiry': 40,
'landscaping': 120,
'roofing': 400
};
// Fuzzy match: check if any key is contained in the field value
for (var key in valueMap) {
if (projectType.indexOf(key) !== -1 ||
key.indexOf(projectType) !== -1) {
conversionValue = valueMap[key];
break;
}
}
}
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': conversionValue,
'currency': 'USD'
});
})();
</script>
// VARIATION: COMPANY SIZE FIELD (B2B SAAS EXAMPLE)
<script>
(function() {
var conversionValue = 100.0; // Default: unknown company size
var sizeField = document.getElementById('company_size') ||
document.querySelector('[name="company_size"]') ||
document.querySelector('select[id*="size"]');
if (sizeField) {
var size = sizeField.value.toLowerCase().trim();
// Map company size brackets to estimated LTV proxies
if (size.indexOf('1-10') !== -1 || size === 'solo') {
conversionValue = 60;
} else if (size.indexOf('11-50') !== -1) {
conversionValue = 150;
} else if (size.indexOf('51-200') !== -1) {
conversionValue = 400;
} else if (size.indexOf('201') !== -1 ||
size.indexOf('enterprise') !== -1 ||
size.indexOf('500+') !== -1) {
conversionValue = 900;
}
}
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': conversionValue,
'currency': 'USD'
});
})();
</script>
These values should not be arbitrary. The most useful implementation maps field values to the actual average deal size or average LTV for that segment, pulled from your CRM or sales data. If your average kitchen renovation job is worth MYR3,500 in revenue and you have a 15% margin, your conversion value for that project type is MYR525. Use real numbers.
Rule 5: Returning Visitor vs New Visitor
Google's native value rules include a "new vs returning customer" condition, but it requires a customer list match or a purchase history signal. For most small and mid-sized businesses that lack a CRM integration or a purchase history list in Google Ads, that condition never fires.
You can approximate it client-side using localStorage, which persists across sessions unlike sessionStorage. A returning visitor who has been to the site before is a warmer lead than a first-time visitor. Not always, but enough to adjust the value signal.
The business logic:
First visit: Unknown intent. Baseline.
Second or third visit: Returning, researching. Moderate uplift.
Four or more visits: High consideration. Strong uplift.
Previously converted (flag set by a prior conversion tag): Repeat buyer or upsell opportunity. Adjust to your business model.
// GTM CUSTOM HTML TAG — RETURNING VISITOR VALUE RULE
<script>
(function() {
var baseValue = 50.0;
var multiplier = 1.0;
// Visit counter lives in localStorage (persists across sessions)
var visitCount = parseInt(localStorage.getItem('cv_visit_count') || 0);
visitCount++;
localStorage.setItem('cv_visit_count', visitCount);
// Has this visitor previously converted?
var hasPreviouslyConverted =
localStorage.getItem('cv_has_converted') === 'true';
// --- Value logic ---
if (hasPreviouslyConverted) {
// Repeat buyer: value depends on your business.
// For subscription businesses, this is an upsell — high value.
// For one-off services, this may be referral activity — medium value.
multiplier = 1.5;
} else if (visitCount >= 4) {
multiplier = 1.35; // Very engaged researcher
} else if (visitCount >= 2) {
multiplier = 1.15; // Returning, still evaluating
}
// First visit: stays at 1.0
// Mark this visitor as converted for future sessions
localStorage.setItem('cv_has_converted', 'true');
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': baseValue * multiplier,
'currency': 'USD'
});
})();
</script>
One important caveat: localStorage is per browser, per device, and is cleared when a user clears their browser data. It is an approximation, not a reliable CRM signal. Use it as a directional input to Smart Bidding rather than a precise measurement. For businesses with a login system or a CRM integration, pushing a known customer flag through the dataLayer is considerably more reliable.
Rule 6: URL Parameter and Landing Page Signals
Sometimes the signal you need is sitting right in the URL, and you just need to read it. A price or quantity parameter from a product configuration tool.
A service tier embedded in the URL from an internal link. A campaign-specific landing page that targets high-value verticals. A URL path that tells you whether the user came from a premium product category or a budget one.
This is particularly useful for e-commerce and configurator-style websites where the URL naturally carries product or service data.
// GTM CUSTOM HTML TAG — URL PATH AND PARAMETER VALUE RULE
<script>
(function() {
var baseValue = 50.0;
var multiplier = 1.0;
var path = window.location.pathname.toLowerCase();
var params = new URLSearchParams(window.location.search);
// --- Path-based rules ---
// Adjust paths to match your site's URL structure
if (path.indexOf('/enterprise') !== -1 ||
path.indexOf('/premium') !== -1) {
multiplier = 1.6; // Enterprise/premium section visitor
} else if (path.indexOf('/small-business') !== -1 ||
path.indexOf('/starter') !== -1) {
multiplier = 0.8; // Starter tier visitor
}
// --- Query parameter rules ---
// Example: ?plan=professional passed from a pricing calculator
var plan = (params.get('plan') || '').toLowerCase();
if (plan === 'enterprise') { multiplier = Math.max(multiplier, 1.8); }
else if (plan === 'professional') { multiplier = Math.max(multiplier, 1.3); }
else if (plan === 'starter') { multiplier = Math.min(multiplier, 0.8); }
// Example: ?quote=15000 passed from a price estimator tool
var quoteAmount = parseFloat(params.get('quote'));
if (!isNaN(quoteAmount) && quoteAmount > 0) {
// Use the actual estimated quote value directly if available
// Apply a margin factor (e.g. 0.15 = 15% margin on estimated quote)
var marginFactor = 0.15;
baseValue = quoteAmount * marginFactor;
multiplier = 1.0; // Reset multiplier — we have a real number
}
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': baseValue * multiplier,
'currency': 'USD'
});
})();
</script>
The quoteAmount pattern at the bottom is the most powerful variant here. If your website has a price calculator or estimator tool that passes the estimated value as a URL parameter, you can feed that directly into the conversion tag as a real financial signal rather than a proxy multiplier. Smart Bidding receiving the estimated quote value is dramatically more useful than a multiplied approximation.
Rule 7: Stacking Multiple Signals
The most sophisticated implementation combines multiple signals into a single, composite multiplier. This is what you would build once the individual rules above have been validated and you want to consolidate them.
The logic is straightforward: each signal contributes a modifier, modifiers are summed, and the result is floored and capped to prevent the composite from producing absurd values in edge cases.
// GTM CUSTOM HTML TAG — COMPOSITE MULTI-SIGNAL VALUE RULE
<script>
(function() {
var baseValue = 50.0;
var totalModifier = 0.0; // Additive modifiers, summed below
// ── Signal 1: Time of day ──────────────────────────────────
var now = new Date();
var hour = now.getHours();
var day = now.getDay();
var isBusinessHours = (hour >= 9 && hour < 18);
var isWeekday = (day > 0 && day < 6);
if (isWeekday && isBusinessHours) { totalModifier += 0.2; }
else if (!isWeekday) { totalModifier -= 0.15; }
// ── Signal 2: Traffic source ───────────────────────────────
var medium = sessionStorage.getItem('cv_medium') || '';
var campaign = sessionStorage.getItem('cv_campaign') || '';
var isBranded = campaign.indexOf('brand') !== -1;
if (medium === 'cpc' && isBranded) { totalModifier += 0.25; }
else if (medium === 'email') { totalModifier += 0.15; }
else if (medium === 'affiliate') { totalModifier -= 0.2; }
// ── Signal 3: Engagement depth ─────────────────────────────
var pages = parseInt(sessionStorage.getItem('cv_pages') || 1);
var highIntent = sessionStorage.getItem('cv_high_intent') === 'true';
if (highIntent) { totalModifier += 0.25; }
if (pages >= 5) { totalModifier += 0.15; }
else if (pages === 1) { totalModifier -= 0.1; }
// ── Signal 4: Returning visitor ────────────────────────────
var visitCount = parseInt(localStorage.getItem('cv_visit_count') || 1);
if (visitCount >= 4) { totalModifier += 0.2; }
else if (visitCount >= 2) { totalModifier += 0.1; }
// ── Composite multiplier: floor 0.5, cap 2.0 ──────────────
var multiplier = Math.min(Math.max(1.0 + totalModifier, 0.5), 2.0);
gtag('event', 'conversion', {
'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYY',
'value': Math.round(baseValue * multiplier * 100) / 100,
'currency': 'USD'
});
})();
</script>
The cap at 2.0x prevents a perfect-signal conversion (branded search, business hours, pricing page visited, returning visitor, deep engagement) from reporting a wildly inflated value that distorts the Smart Bidding target. The floor at 0.5x ensures that even a poor-signal conversion still registers meaningful value rather than approaching zero.
The Math.round at the end cleans up floating-point arithmetic so you are not sending values like 67.500000003 to the platform.
Implementation Notes and Honest Caveats
These are approximations, not measurements
Every multiplier in this article is a proxy. Browser time is not your CRM. Pages visited is not sales qualification. UTM parameters tell you the campaign, not the buyer's intent. These signals are directional inputs to Smart Bidding, not precise financial measurements.
The goal is to give the algorithm a more differentiated signal than a flat value. You do not need to be perfectly accurate to improve bidding performance. You need to be more correlated with actual conversion quality than a constant.
Calibrate against your actual data
Before going live, pull your last ninety days of converted leads or purchases and segment them by the signals you plan to use. Do branded-search leads actually close at a higher rate? Do returning visitors actually produce higher average order values? Validate the hypothesis before encoding it as a multiplier. If the data does not support the signal, do not use it.
Smart Bidding needs time
After making changes to conversion values, give Smart Bidding at least two to four weeks before evaluating the impact. The algorithm takes time to adjust its bid model to the new value distribution. Evaluating performance in the first week after a change is like checking the oven after thirty seconds.
Do not use these on top of a native value rule for the same dimension
If you have already set up a native device or geo value rule in the Google Ads UI for a conversion action, do not also pass a device-adjusted value through a JavaScript tag for the same action. You will be applying two layers of adjustment to the same signal and the results will be unpredictable. Pick one approach per dimension.
Test in GTM Preview before publishing
Every snippet here fires a conversion. A broken conversion tag is significantly more damaging than no tag at all — it can feed garbage values into your bidding model for weeks before you notice. Test every implementation in GTM Preview mode, verify the computed value is what you expect by adding a console.log before the gtag call, and check the Google Ads Tag Diagnostics report after going live.
The Actual Competitive Advantage Here
Most Google Ads accounts, including those managed by agencies, are running flat conversion values. Every lead fires at the same number regardless of when it came in, where it came from, how engaged the visitor was, or what the form field said about the job size.
That is a significant amount of bidding precision being left on the table.
Smart Bidding is a remarkably effective system when it is fed accurate signals. The platform already knows device type and location. What it does not know, unless you tell it, is that the person who visited your pricing page three times over two weeks before submitting a form about an enterprise project is worth twelve times the value of the person who bounced to the contact form after fifteen seconds on your homepage.
You know that. Your CRM knows that. The question is whether your conversion tracking knows it too.
These scripts are how you close that gap.
This article is part of The Digital Briefing's Marketing series. For the full operational framework, read more at www.thedigitalbriefing.com/blogs.
