# How Target Detection Works for Identifying Tracked Domains in Open-SEO

> Learn how Target Detection in Open-SEO uses a four-step heuristic to classify user input as a tracked domain or brand keyword. Understand the process for identifying tracked domains.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-08-07

---

**The `detectTarget` function uses a deterministic four-step heuristic—trimming whitespace, checking for dots without spaces, normalizing via the URL constructor, and validating hostnames—to classify user input as either a tracked domain or a brand keyword.**

Open-SEO, an open-source SEO analytics platform, relies on precise input classification to separate domain-based tracking from keyword-based tracking. The **target detection** system serves as the gatekeeper for the "Add tracked domain" feature, ensuring only valid hostnames enter the rank-tracking pipeline while routing free-text brand queries to the appropriate keyword analysis workflows.

## The Target Detection Algorithm

The detection logic resides in [`src/shared/targetDetection.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.ts) and follows a strict, side-effect-free process that prioritizes speed and reliability.

### Step 1: Input Sanitization

First, the raw input undergoes `rawInput.trim()` to eliminate leading and trailing whitespace, preventing accidental spaces from breaking domain validation.

### Step 2: Quick Domain Heuristic

The algorithm performs a rapid preliminary check: the trimmed string must contain **no spaces** and **must include a dot** (`.`). This filters out obvious brand keywords like "Acme Widgets" before expensive parsing operations begin.

### Step 3: Domain Normalization and Validation

If the heuristic passes, the system invokes `normalizeDomain` from [`src/types/schemas/domain.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/domain.ts). This helper:

- Adds a dummy protocol if missing to satisfy the `URL` constructor
- Extracts the hostname using the browser-compatible `URL` API
- Strips leading `www.` prefixes
- Returns the lower-cased hostname

If this normalization throws an error (invalid URL structure) or the resulting hostname lacks a dot, the input is rejected as a domain and classified as a keyword instead.

### Step 4: Type Classification

Finally, `detectTarget` returns a discriminated union:

- For valid domains: `{ type: "domain", value: hostname }`
- For all other inputs: `{ type: "keyword", value: trimmed }`

## Domain Validation in the Rank-Tracking Pipeline

This detection mechanism is critical for the **tracked domains** feature, where each stored domain associates with specific location and city settings. When users enter competitors for AI-driven searches or add new properties to monitor, the application calls `detectTarget` before any external API requests (such as DataForSEO).

This architecture provides:

- **Consistent input handling** across client components like [`src/client/features/ai-search/BrandLookupPage.tsx`](https://github.com/every-app/open-seo/blob/main/src/client/features/ai-search/BrandLookupPage.tsx) and server services such as [`src/server/features/ai-search/services/brandLookup.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/ai-search/services/brandLookup.ts)
- **Early validation** that prevents malformed URLs from consuming third-party API credits
- **Clear separation** between domain-based rank tracking and keyword-based share-of-voice analysis

## Practical Implementation Examples

The `detectTarget` function handles various input formats uniformly:

```ts
import { detectTarget } from '@/shared/targetDetection';

// Valid domain without protocol
console.log(detectTarget('example.com'));
// → { type: 'domain', value: 'example.com' }

// Domain with protocol, www, and path
console.log(detectTarget('https://www.Example.com/page'));
// → { type: 'domain', value: 'example.com' }

// Brand keyword with spaces
console.log(detectTarget('Acme Widgets'));
// → { type: 'keyword', value: 'Acme Widgets' }

// Malformed input fails normalization
console.log(detectTarget('not a real domain.'));
// → { type: 'keyword', value: 'not a real domain.' }

```

These patterns are validated in the comprehensive test suite located at [`src/shared/targetDetection.test.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.test.ts).

## Key Source Files

Understanding the target detection flow requires familiarity with these specific modules:

- **[`src/shared/targetDetection.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.ts)** – Core detection logic implementing the four-step classification algorithm
- **[`src/shared/targetDetection.test.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/targetDetection.test.ts)** – Unit tests covering edge cases including internationalized domain names and protocol variations
- **[`src/types/schemas/domain.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/domain.ts)** – Contains the `normalizeDomain` helper responsible for hostname extraction and `www.` stripping
- **[`src/server/features/ai-search/services/brandLookup.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/ai-search/services/brandLookup.ts)** – Server-side service demonstrating how detection results route domain entries to competitor analysis
- **[`src/client/features/ai-search/BrandLookupPage.tsx`](https://github.com/every-app/open-seo/blob/main/src/client/features/ai-search/BrandLookupPage.tsx)** – React component utilizing `detectTarget` for real-time input validation in the user interface

## Summary

- **Target detection** in Open-SEO relies on the `detectTarget` helper to classify user input as either a domain or keyword before processing.
- The algorithm uses a deterministic heuristic: no spaces, contains a dot, and successfully parses through `normalizeDomain` using the standard `URL` constructor.
- Valid domains return with type `"domain"` and a cleaned hostname; all other inputs receive type `"keyword"`.
- This pure function operates safely in both server-side API handlers and client-side React components, ensuring consistent behavior across the stack.
- Early validation prevents invalid URLs from reaching external SEO data providers like DataForSEO.

## Frequently Asked Questions

### How does target detection handle URLs containing paths or query parameters?

The `normalizeDomain` function in [`src/types/schemas/domain.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/domain.ts) utilizes the browser-compatible `URL` constructor to extract only the hostname component, automatically discarding paths, query strings, and hash fragments. For example, `https://example.com/blog/post` resolves to `example.com`.

### Does the system treat subdomains differently from root domains?

The detection algorithm preserves subdomains during normalization, only stripping the specific `www.` prefix. Inputs like `blog.example.com` return as type `"domain"` with value `blog.example.com`, maintaining distinct tracking for subdomain-specific SEO strategies.

### Can detectTarget be safely called from server-side Node.js environments?

Yes, the function is deliberately **pure** and side-effect-free, making it safe for server-side services like [`brandLookup.ts`](https://github.com/every-app/open-seo/blob/main/brandLookup.ts). It relies only on standard JavaScript APIs (String methods and the global `URL` constructor) available in both Node.js and browser runtimes.

### What happens when a user enters an invalid domain-like string?

If the input passes the initial heuristic (contains a dot, no spaces) but fails `normalizeDomain` validation—such as `not a real domain.`—the function catches the thrown error and returns `{ type: "keyword", value: "not a real domain." }`, routing the input to keyword tracking instead of domain tracking.