
Marketers love CPC like a toddler loves a shiny toy. “Look! I got my cost per click down to 50 cents!” That’s great, Chad. Did those clicks actually make you any money?
See, the problem with obsessing over CPC is that it ignores the one thing that actually matters: EPC (Earnings Per Click). EPC tells you how much each click is actually worth in cold, hard revenue—not just how cheaply you can buy traffic.
Here’s an example:
Advertiser A pays €1 per click, converts 5% of those clicks, and has an average order value (AOV) of €100.
Advertiser B pays €0.50 per click, converts 1%, and has the same AOV.
EPC math:
Advertiser A: €5 EPC
Advertiser B: €0.50 EPC
Guess who’s making actual money? Not the one flexing their “super low CPCs” on LinkedIn.
Chasing the cheapest clicks without understanding their value is like bragging about scoring a €20 parachute—sure, it was cheap, but you might want to check if it actually opens.
I break this all down in The Ultimate Digital Advertising Library—your all-access pass to Meta and Google Ads (Google Partner) that don’t just look good in a report but actually make money.
A high PPC and EPC could mean that you are running very successful campaigns, while a high PPC and low EPC could mean that you are paying a lot for clicks that are not generating any conversions.
Here are some tips for improving your PPC, CPC, and EPC:
- Choose the right keywords.
- Write effective ad copy.
- Place your ads in the right location.
- Track your results.
Google Ads or other channels doesn't matter EPC
If you keep your eyes on only one metric, it should be earnings per click.
If you’re not studying this metric, if you don’t know how it’s calculated, and if you aren’t using it strategically to influence your business decisions, then you are leaving money on the table.
Earnings per Click (EPC) doesn’t care how high your conversion rate is, the number of clicks you generate or how much you get paid.
This metric cuts through the statistical clutter and gives you the exact amount of money you can expect to receive for each click you send to an offer.
This is key, whether you are acting as an affiliate or purchasing clicks, like Google Ads.
In the case of acting as an affiliate, you already have a good idea of how much traffic you can drive from your list.
If you can routinely send 300 people to an offer, then all else being equal, knowing the EPC can tell you which offer to promote.
For example, if one offer is paying $1.22 per click, and the other offer is paying $4.81 per click, it makes it pretty clear which offer you should spend your time on. Also for Google Ads campaigns if you see that in a particular campaign the EPC is much higher than another campaign.
And as a product owner purchasing clicks, you can compare the cost per click to the earnings per click to determine if purchasing clicks will make or lose you money.
Like, if you have a campaign with products with different price tiers, then you have to understand if you do not pay too much CPC in comparison to EPC.
We have a Google Ads script for that:
// ======= Configuration Constants ======= //
const MCC_SHEET_URL = 'MCC_SHEET_URL_HERE';
const FOLDER_ID = 'FOLDER_ID_HERE’; // Folder ID for storing spreadsheets
// Column indices in MCC Sheet (0-based)
const COLUMN_CLIENT_ID = 1; // Column B: datasource_google_ads
const COLUMN_CHECKBOX = 18; // Column S: Checkmark
const COLUMN_SHEET_URL = 19; // Column T: Spreadsheet URL
const REPORT_FIELDS = {
QUERY: 'Query',
CAMPAIGN_NAME: 'CampaignName',
AVG_CPC: 'AverageCpc',
COST: 'Cost',
CONVERSIONS: 'Conversions',
CLICKS: 'Clicks'
};
function main() {
try {
// Step 1: Open MCC sheet and access 'Google Ads - Client ID' tab
const mccSpreadsheet = SpreadsheetApp.openByUrl(MCC_SHEET_URL);
const mccSheet = mccSpreadsheet.getSheetByName('Google Ads - Client ID');
if (!mccSheet) {
throw new Error("Sheet 'Google Ads - Client ID' not found in MCC_SHEET_URL.");
}
const lastRow = mccSheet.getLastRow();
if (lastRow < 2) {
Logger.log("No data to process in 'Google Ads - Client ID' sheet.");
return;
}
// Step 2: Read all data from the 'Google Ads - Client ID' sheet
const mccData = mccSheet.getRange(2, 1, lastRow - 1, 13).getValues(); // From row 2, columns A to M (1 to 13)
// Iterate through each row in the MCC sheet
for (let i = 0; i < mccData.length; i++) {
const rowNumber = i + 2; // Actual sheet row number (starting from 2)
const isChecked = mccData[i][COLUMN_CHECKBOX]; // Column L: Checkbox
if (!isChecked) continue; // Skip if not checkmarked
const clientID = mccData[i][COLUMN_CLIENT_ID]; // Column B: datasource_google_ads
let clientSheetUrl = mccData[i][COLUMN_SHEET_URL]; // Column M: Spreadsheet URL
Logger.log(`Processing row ${rowNumber}: clientID=${clientID}`);
// Create a new spreadsheet if Column M is empty
if (!clientSheetUrl) {
try {
clientSheetUrl = createClientSpreadsheet(clientID);
mccSheet.getRange(rowNumber, COLUMN_SHEET_URL + 1).setValue(clientSheetUrl); // Column M: 13th column (1-based)
Logger.log(`Created new spreadsheet for client ${clientID}: ${clientSheetUrl}`);
} catch (error) {
Logger.log(`Error creating spreadsheet for client ${clientID} at row ${rowNumber}: ${error.message}`);
continue; // Skip processing this client if sheet creation failed
}
}
// Select the account via MCC and process it
try {
const accountIterator = MccApp.accounts().withIds([clientID]).get();
if (!accountIterator.hasNext()) {
Logger.log(`No account found with Client ID ${clientID} at row ${rowNumber}.`);
continue;
}
AdsManagerApp.select(accountIterator.next());
const accountConfig = { id: clientID, sheetUrl: clientSheetUrl };
process (accountConfig);
} catch (error) {
Logger.log(`Error processing account ${clientID} at row ${rowNumber}: ${error.message}`);
continue; // Skip to the next client if account processing failed
}
}
Logger.log("Script execution completed successfully.");
} catch (error) {
Logger.log (`Error in main function: ${error.message}`);
}
}
function createClientSpreadsheet(clientID) {
// Create a new spreadsheet for the client and move it to the specified folder
const newSpreadsheet = SpreadsheetApp.create(`Client Report - ${clientID}`);
const newSpreadsheetUrl = newSpreadsheet.getUrl();
// Move the newly created spreadsheet to the specified folder
const file = DriveApp.getFileById(newSpreadsheet.getId());
DriveApp.getFolderById(FOLDER_ID).addFile(file);
DriveApp.getRootFolder().removeFile(file); // Remove from root folder
Logger.log(`New spreadsheet created and moved to folder for client ${clientID}: ${newSpreadsheetUrl}`);
return newSpreadsheetUrl;
}
function process(CONFIG) {
Logger.log(`Processing data for Client ID: ${CONFIG.id}`);
const sheet = SpreadsheetApp.openByUrl(CONFIG.sheetUrl).getActiveSheet();
sheet.clear();
sheet.appendRow(['CPC Range', 'Avg CPC', 'Conversion Volume', 'CPA Performance', 'Total Cost', 'Total Clicks']);
const endDate = Utilities.formatDate(new Date(), AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd');
const startDate = Utilities.formatDate(new Date(Date.now() - 30 * 86400000), AdsApp.currentAccount().getTimeZone(), 'yyyyMMdd');
const reportQuery = `SELECT ${Object.values(REPORT_FIELDS).join(', ')}
FROM SEARCH_QUERY_PERFORMANCE_REPORT
WHERE Clicks >= 10 DURING ${startDate},${endDate}
ORDER BY ${REPORT_FIELDS.AVG_CPC} ASC`;
const report = AdsApp.report(reportQuery);
const rows = report.rows();
const currency = '€'; // Use Euro
const cpcRanges = getCpcRanges(currency);
const rangeData = cpcRanges.reduce((acc, range) => (acc[range.range] = {
clicks: 0,
cost: 0,
conversions: 0,
totalCpc: 0
}, acc), {});
while (rows.hasNext()) {
const row = rows.next();
const avgCpc = parseFloat(row[REPORT_FIELDS.AVG_CPC]);
const cost = parseFloat(row[REPORT_FIELDS.COST]);
const conversions = parseFloat(row[REPORT_FIELDS.CONVERSIONS]);
const clicks = parseInt(row[REPORT_FIELDS.CLICKS]);
const range = cpcRanges.find(r => avgCpc >= r.min && avgCpc < r.max);
if (range) {
const data = rangeData[range.range];
data.clicks += clicks;
data.cost += cost;
data.conversions += conversions;
data.totalCpc += avgCpc * clicks;
}
}
const rowsToWrite = [];
Object.entries(rangeData).forEach(([range, data]) => {
if (data.clicks > 0) {
const avgCpc = data.totalCpc / data.clicks;
const cpa = data.conversions > 0 ? data.cost / data.conversions : 0;
rowsToWrite.push([
range,
formatCurrency(avgCpc, currency),
data.conversions,
formatCurrency (cpa, currency),
formatCurrency (data.cost, currency),
data.clicks
]);
}
});
sheet.getRange(2, 1, rowsToWrite.length, rowsToWrite[0].length).setValues(rowsToWrite);
Logger.log(`Data written to spreadsheet for Client ID: ${CONFIG.id}`);
}
function getCpcRanges(currency) {
// Define custom CPC ranges
return [
{ min: 0, max: 0.5, range: `From ${currency}0 to ${currency}0,50` },
{ min: 0.51, max: 1, range: `From ${currency}0,51 to ${currency}1,00` },
{ min: 1.01, max: 1.5, range: `From ${currency}1,01 to ${currency}1,50` },
{ min: 1.51, max: 2, range: `From ${currency}1,51 to ${currency}2,00` },
{ min: 2.01, max: 3, range: `From ${currency}2,01 to ${currency}3,00` },
{ min: 3.01, max: Infinity, range: `From ${currency}3,01 and above` }
];
}
function formatCurrency(value, currency) {
return `${currency} ${parseFloat(value).toFixed(2)}`;
}
-
Reads Client Data from MCC Google Sheet:
- It accesses a Google Sheets document (
MCC_SHEET_URL
) that contains client Google Ads account IDs. - The sheet should have:
- Client IDs (Google Ads account numbers)
- Checkmarks (to indicate which accounts to process)
- Spreadsheet URLs (where data should be stored)
- It accesses a Google Sheets document (
-
Creates Google Sheets for Clients (if not available):
- If a spreadsheet URL is missing, it creates a new Google Sheet for the client.
- Moves the new sheet to a designated folder (
FOLDER_ID
).
-
Retrieves Search Query Performance Data:
- Connects to each selected Google Ads account via MCC.
- Runs a Google Ads Query Language (GAQL) query on the
SEARCH_QUERY_PERFORMANCE_REPORT
for the last 30 days. - Filters data where Clicks ≥ 10 to focus on relevant queries.
-
Processes and Categorizes Data:
- Extracts key metrics:
- Search Query
- Campaign Name
- Average CPC (Cost-Per-Click)
- Total Cost
- Total Conversions
- Clicks
- Groups CPC values into predefined CPC Ranges:
- €0 - €0.50
- €0.51 - €1.00
- €1.01 - €1.50
- €1.51 - €2.00
- €2.01 - €3.00
- €3.01 and above
- Calculates CPA (Cost per Acquisition) based on conversions.
- Extracts key metrics:
-
Writes Processed Data to Client Google Sheets:
- Clears the Google Sheet.
- Writes a structured table with:
- CPC Range
- Average CPC
- Conversion Volume
- CPA Performance
- Total Cost
- Total Clicks
Key Functions in the Script:
-
main()
– The main function that:- Reads MCC spreadsheet data.
- Loops through checked clients.
- Creates spreadsheets if missing.
- Selects and processes each client’s Google Ads account.
-
createClientSpreadsheet(clientID)
– Creates a new Google Sheet for the client and moves it to a specific folder. -
process(CONFIG)
– Extracts Google Ads data, categorizes CPC ranges, calculates performance metrics, and writes data to the client’s Google Sheet. -
getCpcRanges(currency)
– Defines CPC price range groupings. -
formatCurrency(value, currency)
– Formats numbers as currency (e.g.,€ 1.50
).
Calculating earnings per click works like this: Net profit per click = earnings per click - cost per click.
Forget conversion rates, click through rates and payouts. If your earnings per click is higher than your cost per click, you’re making money.
Most advertising platforms will provide you with the cost per click or a way to calculate it. And most affiliate platforms will also tell you the earnings per click.
Of course, if you’re promoting your own product, you’ll need to determine your approximate earnings per click. This isn’t simply the money you earn from the initial promotion.
When you purchase advertising and make sales, you are building your list of buyers. Some of these buyers will continue to make purchases in the future, and adding this factor to your earnings per click will allow you to pay more money per click while still staying in profit.
Of course, if finances are tight, you’ll want to make more up front on your clicks than you spend. But once you’ve banked some profits, you’ll be able to use that money to increase the price you pay for clicks as needed, thereby growing your list of buyers even faster.
And if you also capture email addresses of people who visited your sales page but didn’t buy, and if some of those go on to become buyers later, these are additional profits you can factor into your calculations.
To calculate earnings per click (EPC) take the total earnings you have generated over a period of time, and then divide that by the number of clicks you have generated for that same period.
This is an estimate of what you can expect each individual click you are generating to product in earnings.