/

How to Segment Branded vs Non-Branded Traffic for SEO

This guide explains how to separate branded from non-branded traffic and optimise your SEO strategy accordingly. It provides concrete, step-by-step instructions for both using SEOmonitor and, in case you’re stuck using other tools, handling the process manually.

Understanding Branded and Non-Branded Keywords

Branded Keywords

These include your company name, product names, or common variations. For instance, terms like “Nike shoes” or “iPhone 17” directly reference a brand, but they might not be considered a ‘brand’ term. If you’re Apple, then “iPhone 17” is undoubtedly a branded term. However, if you merely happen to sell iPhones, then generally, we wouldn’t consider it a branded term for you because users don’t behave differently when interacting with you or searching for that term compared to any other company. There are certain instances where you might want to rank for other companies’ brand terms—for example, if we wanted to rank for ahrefs terms. These are what we’d consider ‘brands of others.’ True branded terms are navigational. They’re ones where the user intends to end up on your site. They usually generate higher conversion rates and indicate users who are already familiar with your brand.

Non-Branded Keywords

These are generic search terms related to your products or services, such as “running shoes” or “smartphone reviews.” They usually have higher search volumes and can be found all through the purchase funnel though less often, as the user refines their searches, at the bottom of it.

Why Segmentation Matters

Brand and non-brand traffic behaves very differently from each other. Branded traffic is heavily effected by other advertising activities in a way that non-brand traffic typically isn’t. Branded traffic’s click-through-rate tends not to be effected by brand scandals, whilst we’d expect non-branded CTR to reduce for unloved brands. More than that, the branded traffic obscures the traffic that you’re able to optimise, control and improve. Accurate segmentation leads to:

  • Accurate Performance Measurement: Separating these metrics ensures you report growth from your own SEO efforts—not external factors like TV ads or PR.
  • Better Resource Allocation: Understand which keywords are driving conversions and adjust your strategy accordingly.
  • Improved Competitor Analysis: Compare your non-branded search market share against competitors and identify opportunities to capture branded traffic from industry peers.
  • Clear Customer Journey Mapping: Track users as they move from generic queries to brand-specific searches, refining your funnel at every stage.

You’d be surprised how many SEO agencies, and practitioners more generally, aren’t bucketing organic traffic this way. That’s because it can be a pain to do, but it doesn’t have to be.

How to Segment Brand & Non-Brand Traffic with SEOmonitor

SEOmonitor makes it really easy to segment organic traffic into brand and non-brand buckets; it’s built into the fabric of the tool. Here’s how to get to a slide-ready visualisation in less than 10 minutes:

  1. Create a new campaign: start a free trial.
  2. Define Your Brand Terms: Input your brand name(s), product names, common misspellings, and related variations into SEOmonitor. You can change these later.
  3. Connect GA and GSC: four clicks and you’re done here.
  4. Make some tea:
    1. Coffee is also acceptable, but it’s difficult to review data without some sort of drink.
    2. In the background SEOmonitor’s automatically mapping data and preparing your visualisation.
  5. Click into the organic traffic module: You’re done!

How to Segment Without SEOmonitor

Manual segmentation is possible, but requires multiple steps and careful handling of data. Here’s an outline of the process:

1. Data Export: Brand traffic typically changes slowly and so having a long-term way to store changes is important to fully understanding trends. Google Search Console only stores 18 months of data though, so you’ll want to export data for long-term storage to BigQuery. This is a multi-stage process, which is more completely covered in Google’s documentation here, but the basics are:

  1. Sign up for Google Cloud
  2. Create a new project
  3. Enable BigQuery in that project
  4. Within IAM and Admin add serach-console-data-export@system.gserviceaccount.com as a new principal with BigQuery Job User and BigQuery Data Editor roles.
  5. In Search Console go to settings > Bulk data export.
  6. Enter the Google Cloud project ID, a dataset name and a location for the data to be held.
  7. Wait 48 hours for the first export to occur.There’s a small chance that there’ll be a small cost attached to this. If other teams are maxing out the otherwise very generous BigQuery credits Google Cloud provides by default then you may need to pay for these extracts. Check in on who else, within your organisation, is using BigQuery.

2. Import the data into Google Sheets: Whilst you could use the data from Big Query directly in Looker Studio via the BigQuery connector, this will increasingly lag and timeout as your dataset grows over time. As such, the most performant solution is to have a Google Sheet update with the statistics you need once a day and then pull from that. Whilst you could use what Google calls ‘Connected Sheets‘ for this, it’s prone to issues and so you’re usually better off adding in a quick script.

  1. Click on ‘tools’, then ‘script editor’ within Google Sheets
  2. Paste in the following script:
/**
 * Fetches data from a Google Search Console BigQuery export,
 * calculates traffic metrics (total, regex-matched, non-matched),
 * and appends the data to a Google Sheet.
 */
function updateSheetFromBigQuery() {
  // --- CONFIGURATION (CHANGE THESE) ---
  const projectId = 'your-gcp-project-id'; // Your Google Cloud Project ID.
  const datasetId = 'your_gsc_dataset';       // Your BigQuery dataset (GSC export).
  const sheetName = 'Sheet1';           // Sheet name to append to.
  const regexPattern = '.*your_brand_term.*';  // *** YOUR REGEX HERE *** (e.g., '.*Nike.*').

  // --- END CONFIGURATION ---

  try {
    // 1. Find the latest table (GSC exports have date-suffixed tables).
    const tables = BigQuery.Tables.list(projectId, datasetId).tables;
    if (!tables || tables.length === 0) {
      throw new Error(`No tables found in dataset ${datasetId}.`);
    }

    tables.sort((a, b) => Number(b.creationTime) - Number(a.creationTime));
    const latestTableId = tables[0].tableReference.tableId;
    Logger.log('Using table: ' + latestTableId);

    // 2. Construct the query.
    const query = `
      WITH
        LatestDateData AS (
          SELECT
            MAX(date) as latest_date
          FROM
            \`${projectId}.${datasetId}.${latestTableId}\`
        ),
        DailyData AS (
          SELECT
            SUM(impressions) + SUM(clicks) AS total_traffic,
            SUM(CASE WHEN REGEXP_CONTAINS(query, r"${regexPattern}") THEN impressions + clicks ELSE 0 END) AS matched_traffic
          FROM
            \`${projectId}.${datasetId}.${latestTableId}\`
          CROSS JOIN
            LatestDateData
          WHERE
            date = LatestDateData.latest_date
        )
      SELECT
        (
          SELECT
            latest_date
          FROM
            LatestDateData
        ) AS latest_date,
        total_traffic,
        matched_traffic,
        total_traffic - matched_traffic AS non_matched_traffic
      FROM
        DailyData;
    `;

    // 3. Run the BigQuery query.
    const request = {
      query: query,
      useLegacySql: false,
    };

    let queryResults = BigQuery.Jobs.query(request, projectId);
    const jobId = queryResults.jobReference.jobId;

    // Check if query is complete (with exponential backoff).
    let sleepTimeMs = 500;
    while (!queryResults.jobComplete) {
      Utilities.sleep(sleepTimeMs);
      sleepTimeMs *= 2;
      queryResults = BigQuery.Jobs.getQueryResults(projectId, jobId);
    }

    // 4. Get the results and headers.
    const rows = queryResults.rows;
    const schema = queryResults.schema;

     if (!rows) {
        // Handle the case where no rows are returned
        const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
        const sheet = spreadsheet.getSheetByName(sheetName);
        //Don't clear
        // sheet.clearContents();
        sheet.appendRow(["No data returned from BigQuery."]);
        Logger.log("No data returned from BigQuery.");
        return;
    }

    const headers = schema.fields.map(field => field.name);


    // 5. Prepare the data for the sheet.
    const data = []; // Don't include headers here, we'll handle them separately.
    for (let i = 0; i < rows.length; i++) {
      const row = rows[i];
      const sheetRow = [];
      for (let j = 0; j < row.f.length; j++) {
        let cellValue = row.f[j].v;
        if (cellValue === null) {
          cellValue = ''; // Handle null values.
        }
        sheetRow.push(cellValue);
      }
      data.push(sheetRow);
    }

    // 6. Get the sheet and append the new data.
    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    const sheet = spreadsheet.getSheetByName(sheetName);

    if (!sheet) {
      throw new Error(`Sheet "${sheetName}" not found.`);
    }

    // Check if the sheet is empty.  If so, add the headers.
    if (sheet.getLastRow() === 0) {
      sheet.appendRow(headers);
    }

    // Append the data rows.
    sheet.getRange(sheet.getLastRow() + 1, 1, data.length, data[0].length).setValues(data);

    // 7. (Optional) Add a timestamp (you might not want this if appending daily).
    // const now = new Date();
    // sheet.appendRow(['Last Updated: ' + now.toLocaleString()]);

    Logger.log('Data appended successfully.');

  } catch (error) {
    Logger.log('Error: ' + error.toString());
     const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    const sheet = spreadsheet.getSheetByName(sheetName);
    //Don't clear contents here
    if (sheet) {
        // sheet.clearContents();
        sheet.appendRow(['Error updating data from BigQuery:']);
        sheet.appendRow([error.toString()]);
    }
  }
}

/**
 * Creates a time-based trigger to run the updateSheetFromBigQuery function daily.
 * Run this function ONLY ONCE to set up the trigger.
 */
function createDailyTrigger() {
  // Delete any existing triggers for this function.
  const triggers = ScriptApp.getProjectTriggers();
  for (const trigger of triggers) {
    if (trigger.getHandlerFunction() === 'updateSheetFromBigQuery') {
      ScriptApp.deleteTrigger(trigger);
    }
  }

  // Create a new trigger.
  ScriptApp.newTrigger('updateSheetFromBigQuery')
    .timeBased()
    .everyDays(1)
    .atHour(1)  // Run around 1 AM (adjust).
    .create();
    Logger.log("Created Daily Trigger");
}
  1. Modify the projectId, datasetId, sheetName and regexPattern variables in the script.
  2. Run createDailyTrigger and authorise it to run unattended.
  3. Run updateSheetFromBigQuery to get some initial data into the Google Sheet. Now the Google Sheet should update daily with the data from BigQuery

3. Filtering Using Regex:

  1. Go back into the script you just worked on.
  2. Edit regexPattern to cover every scenario you can think of for brand terms using Regex to capture different variations (e.g., “Nike,” “Nikee,” “N1ke”). You’re best testing these within GSC before saving so that you know the output is predictable.

4. Dashboard Setup:

  1. Create custom dashboards in Looker Studio by linking your spreadsheet by using the Google Sheets integration.
  2. Based on the new dataset, create visualisations for non-brand traffic, branded traffic and % of each.

Practical Takeaways

Using SEOmonitor: The tool automates segmentation, reduces manual errors, and provides clear, actionable insights—saving you time and resources.

Manual Approach: While feasible, manual segmentation requires exporting data, managing complex spreadsheets, and using regex or scripts for data filtering. This process is significantly more complicated and can be less reliable over time.

By using SEOmonitor, you streamline the entire process and ensure your SEO efforts are accurately measured, leaving you free to focus on strategic decision-making.

Conclusion

Separating branded and non-branded traffic is essential for an accurate picture of your SEO performance. Whether you choose the automated power of SEOmonitor or a manual approach, clear segmentation will help you allocate resources effectively, identify growth opportunities, and stay ahead of competitors. For most teams, investing in a specialised tool like SEOmonitor is a practical, cost and time-saving choice to ensure efficiency and accuracy in tracking and optimising organic search performance.

James Finlayson

James Finlayson

James Finlayson is an SEO and content strategy expert with a diverse background in agency, in-house, and freelance roles. He led award-winning teams at Verve Search and later led organic search at the7stars. Since 2008, he’s partnered with brands like Uber, Babylon Health, The BBC, and Expedia, and has spoken at over 30 international events including TEDx Kingston and SMX London. His work spans every stage of campaign development, earning multiple industry awards.

Inside this article