How to Customize or Extend Site Audit Functionality in OpenSEO: 6 Developer Methods Explained
Developers can customize OpenSEO's site audit functionality by modifying the modular workflow phases in src/server/workflows/, extending the AuditConfig schema, injecting custom logic into crawl policies, or adding new multipage issue detectors—all without altering the core orchestration infrastructure.
OpenSEO is an open-source SEO platform that runs site audits through a durable, step-based workflow architecture. Because each audit phase is implemented as a pure function that receives a typed WorkflowStep and configuration object, the system provides clear extension points for custom business logic, data sources, and reporting requirements.
Understanding the Site Audit Workflow Architecture
The audit pipeline in OpenSEO is explicitly divided into isolated phases defined in src/server/workflows/siteAuditWorkflowPhases.ts. Each phase handles a specific concern, making the system predictable to extend.
| Phase | Responsibility | Primary Implementation |
|---|---|---|
| Discovery | Fetches sitemap/robots.txt and seeds the crawl frontier | runDiscoveryPhase in siteAuditWorkflowPhases.ts |
| Crawl | Fetches pages in chunks, persists data, and records internal links | runCrawlPhase / runCrawlChunk in siteAuditWorkflowCrawl.ts |
| Lighthouse | Runs configurable Lighthouse checks on sampled pages | runLighthousePhase in siteAuditWorkflowPhases.ts |
| Multipage Checks & Finalization | Runs aggregate issue detectors and closes the audit | runMultipageChecks and finalizeAudit in siteAuditWorkflowPhases.ts |
Because these phases communicate via well-defined parameter contracts rather than shared mutable state, you can override or augment any specific behavior while preserving the surrounding orchestration.
Method 1: Configure Audit Behavior via AuditConfig
The simplest way to customize audit behavior is through the AuditConfig object passed when creating an audit via the POST /api/audit endpoint. The configuration flows through runAuditPhases to every downstream phase.
Key configuration fields include:
maxPages– Upper bound on pages the crawler visits (default:1000)lighthouseStrategy– Sampling strategy:"none","sample", or"full"crawlDepth– Optional limit for internal link depth
To adjust a specific audit, modify the JSON payload:
{
"projectId": "abc123",
"startUrl": "https://example.com",
"config": {
"maxPages": 5000,
"lighthouseStrategy": "none"
}
}
The runDiscoveryPhase, runCrawlPhase, and runLighthousePhase functions receive this config object and adjust their behavior accordingly.
Method 2: Modify Crawl Policies with Custom Queuing Logic
The crawl phase decides whether to queue a URL via shouldQueueCrawlLink in src/server/workflows/siteAuditWorkflowCrawl.ts (lines 60-68). This function performs origin checks, robots.txt validation, and URL crawlability tests.
To implement a custom filtering rule—such as excluding specific path patterns—modify the logic after the base checks:
// src/server/workflows/siteAuditWorkflowCrawl.ts
function shouldQueueCrawlLink(
link: string,
origin: string,
robots: RobotsResult,
): boolean {
const baseChecks = isSameOrigin(link, origin)
&& isCrawlableUrl(link)
&& robots.isAllowed(link);
// Custom exclusion: skip admin paths
const isAdminPath = /\/admin\//.test(link);
return baseChecks && !isAdminPath;
}
Because runCrawlPhase calls shouldQueueCrawlLink for every discovered link, your custom logic immediately affects crawl scope without touching the chunking or persistence layers.
Method 3: Extend Lighthouse Sampling Strategies
Lighthouse execution is controlled by runLighthousePhase, which delegates page selection to selectLighthouseSample in src/server/lib/audit/lighthouse.ts. By default, this supports "sample" and "full" strategies.
To implement a custom sampling algorithm:
- Update the
AuditConfigtype definition insrc/types/schemas/audit.tsto include your new strategy literal (e.g.,"full-slow") - Add a branch in
selectLighthouseSampleto handle the new case
// src/server/lib/audit/lighthouse.ts
export function selectLighthouseSample(
pages: { url: string; statusCode: number }[],
startUrl: string,
strategy: AuditConfig["lighthouseStrategy"]
) {
if (strategy === "full-slow") {
// Return all crawled pages without sampling
return pages.map(p => p.url);
}
// Existing strategies...
if (strategy === "sample") {
// Default sampling logic
}
}
runLighthousePhase automatically forwards the strategy value, so no orchestration changes are required.
Method 4: Add Custom Multipage Issue Detectors
After crawling completes, runMultipageChecks aggregates issues by calling detectors exported from src/server/lib/audit/issues/multipage/index.ts. Each detector must implement the DetectedIssue contract.
To add a custom check that flags pages with slow load times:
// src/server/lib/audit/issues/multipage/slow-pages.ts
import type { DetectedIssue } from "@/server/lib/audit/issues/page-reporters";
export async function detectSlowPages(auditId: string): Promise<DetectedIssue[]> {
const pages = await AuditRepository.getPagesForAudit(auditId);
return pages
.filter(p => p.loadTimeMs && p.loadTimeMs > 3000)
.map(p => ({
issueType: "slow-page" as const,
pageId: p.id,
pageUrl: p.url,
dedupeKey: p.url,
details: { loadTimeMs: p.loadTimeMs },
}));
}
// Export from the multipage index
// src/server/lib/audit/issues/multipage/index.ts
export { detectSlowPages } from "./slow-pages";
Once exported, runMultipageChecks (called within finalizeAudit) automatically includes your detector in the next audit execution.
Method 5: Hook Into the Finalization Step
The finalizeAudit function in src/server/workflows/siteAuditWorkflowPhases.ts handles the last database writes, emits analytics events, and clears the scratchpad (lines 71-86). This is the ideal location to add custom side effects like notifications or webhook calls.
To send a Slack notification when an audit completes:
// Inside finalizeAudit in siteAuditWorkflowPhases.ts
await captureServerEvent({
distinctId: billingCustomer.userId,
event: "site_audit:complete",
organizationId: billingCustomer.organizationId,
properties: { auditId, status: "completed" },
});
// Custom side-effect
await fetch("https://hooks.slack.com/services/...", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: `✅ Audit ${auditId} finished successfully.` }),
});
await AuditProgressKV.clear(auditId);
Because finalizeAudit runs inside the same Cloudflare Worker durable execution, additional async operations are automatically checkpointed and resumed if the workflow pauses.
Method 6: Create Entirely New Workflow Phases
If the built-in phases don't cover your requirements—for example, adding a pre-crawl HTML validation step—you can create a new phase that follows the established function signature.
New phases must accept a WorkflowStep and a typed parameter object, returning Promise<void>:
// src/server/workflows/siteAuditWorkflowPhases.ts (new function)
export async function runHtmlValidationPhase(
step: WorkflowStep,
params: {
auditId: string;
workflowInstanceId: string;
startUrl: string;
config: AuditConfig;
}
): Promise<void> {
// Custom validation logic here
const validationErrors = await validateHtml(startUrl);
await AuditRepository.saveValidationErrors(params.auditId, validationErrors);
}
Then insert the phase into the main orchestration function runAuditPhases between existing calls, and add a corresponding step configuration in src/server/workflows/auditStepConfigs.ts:
await runDiscoveryPhase(step, { auditId, workflowInstanceId, startUrl, config });
await runHtmlValidationPhase(step, { auditId, workflowInstanceId, startUrl, config }); // New
await runCrawlPhase(step, { auditId, workflowInstanceId, config });
Summary
- Configuration-driven customization: Pass custom values via
AuditConfigto control crawl limits and Lighthouse sampling without code changes. - Crawl policy extension: Modify
shouldQueueCrawlLinkinsiteAuditWorkflowCrawl.tsto inject custom URL filtering logic. - Lighthouse sampling: Add new strategies to
selectLighthouseSampleinlighthouse.tsfor custom page selection algorithms. - Multipage issue detection: Export new detectors from the
multipage/index.tsaggregate to run custom cross-page validations. - Finalization hooks: Extend
finalizeAuditto add notifications, webhooks, or custom reporting triggers. - New workflow phases: Implement functions matching the
(step, params) => Promise<void>signature to add entirely new audit stages.
Frequently Asked Questions
How do I limit the crawl to specific subdirectories?
Modify the shouldQueueCrawlLink function in src/server/workflows/siteAuditWorkflowCrawl.ts to test URL patterns against a whitelist. For example, add a check like new URL(link).pathname.startsWith('/allowed-path') before returning true. This runs during the crawl phase for every discovered link without affecting the discovery or Lighthouse phases.
Can I disable Lighthouse checks entirely for certain audits?
Yes. Set lighthouseStrategy to "none" in the AuditConfig when calling the audit creation endpoint. The runLighthousePhase function in siteAuditWorkflowPhases.ts checks this value and skips execution when the strategy is "none", allowing faster audits that focus solely on crawl data.
Where should I store custom audit data that persists between workflow steps?
Use the AuditScratchpad durable object located in src/server/features/audit/AuditScratchpad.ts to store temporary state across workflow steps. For data that needs to survive the audit completion, write to the AuditRepository in src/server/features/audit/repositories/AuditRepository.ts, which provides the database abstraction for persisting pages, links, and issue records.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →