All work

HubSpot Audit Engine

A zero-dependency Chrome extension that audits years of HubSpot automation debt in one click, then writes the Excel workbook itself in vanilla JavaScript.

Role
Architect & sole engineer
Timeline
2026
Status
In production
Context
A GTMinds product: internal accelerator and lead magnet
Chrome Extension (MV3)Vanilla JavaScriptCustom OOXML/XLSX encodern8n (lead capture)

// stage.problem

Mature HubSpot portals carry years of automation debt

After a few years and a few admins, a HubSpot portal accumulates hundreds of workflows nobody remembers, lists that reference dead properties, and automations that overwrite each other’s fields. Auditing this manually takes days of clicking; agencies charge accordingly.

The official Automation API does not expose the data that matters most: enrollment counts, error states, the actual step logic. A one-click audit could not be built the sanctioned way, so I built it inside the browser session instead.

// stage.system

Read the session, mine the internal API, write the workbook

System architecture · one click to workbook
Logged-inHubSpot session(user’s own tab)MV3 contentscriptCSRF from cookiere-entrancy lockInternal CRMsearch endpointstable sort +dedupe pagingobject 0-44Audit modulesworkflows · listsprops · formsisolated try/catchDiagnosticsJaccard duplicateclusters · write-conflict detectionOOXML/XLSXencoderZIP + CRC-32, 0 depsBrandedworkbook12 tabsin-browserEmail gate→ n8n webhook(external build)No API tokens. No OAuth app.Portal data never leavesthe browser.

A Manifest V3 content script runs inside the user’s authenticated HubSpot tab; the extension never sees credentials. It reads the CSRF token from a non-HttpOnly cookie and pages through HubSpot’s internal CRM search endpoint (object type 0-44, the one place enrollment and error metrics actually live) with a stable sort and dedupe pass, because unstable pagination silently corrupts counts.

Audit modules for workflows, properties, lists, forms, and flow maps each run isolated in try/catch: one module failing degrades the report instead of killing it. Diagnostics are pure functions over the collected rows: on/off/dormant/never-enrolled classification, error detection, Jaccard-similarity clustering to flag near-duplicate workflows for consolidation, and conflict detection that flags workflows racing to write the same property.

The output is a branded multi-tab Excel workbook: diagnostic headlines, remediation plan, consolidation map, cleanup queue, full inventories. It is generated entirely client-side by a hand-written OOXML emitter with its own ZIP container and CRC-32 implementation. No SheetJS, no dependencies, nothing to audit but my own code. The public variant adds an email gate that posts to an n8n webhook; the internal variant skips it.

// stage.product.view

One click in, one workbook out

HubSpot Audit EngineONE-CLICK PORTAL AUDITopen HubSpotReads your logged-in HubSpot session.No token. Nothing leaves your browser.INCLUDE IN AUDIT (WORKFLOWS ALWAYS ON)ListsProperties+ fill-rate (slow)FormsFlow maps (slower)Run auditExcel + CSV download automaticallyDuplicate-cluster count shown as a previewMV3 · 0 DEPENDENCIES · CLIENT-SIDE XLSXHubSpot Workflow Audit~230 AUTOMATION FLOWS INDEXED · IDENTIFIERS REMOVEDHEADLINE FINDINGSTotal automation flows indexed~230Active (ON)~130Inactive (OFF)~85Flagged errors10+Active but dormant (0 enrolled, 7d)~75Never enrolled anyone<10NEAR-DUPLICATE CLUSTERS~50%of flows sit in a duplicate clusterRECOMMENDED ACTIONSFix or kill flagged errorsHIGHRetire never-enrolled flowsMEDConsolidate duplicate clustersMEDArchive retired OFF flowsLOWDiagnosticRemediationConsolidationCleanup QueueAll Workflows+7
POPUP AND WORKBOOK RECREATED FROM A REAL AUDIT OUTPUT · PORTAL AND CLIENT IDENTIFIERS REMOVED

// stage.key.decisions

Why it’s built this way

Decision 01

Chrome extension over OAuth app

Chose
Content script in the user’s own session
Over
A hosted app using HubSpot’s public APIs

The public Automation API omits enrollment metrics and step logic, which are the audit’s entire value. Running in the user’s session accesses what the HubSpot UI itself uses, requires no scopes review, and keeps client data in the client’s browser. The trade-off is fragility against internal API changes; that risk is priced in and monitored.

Decision 02

Hand-rolled XLSX over SheetJS

Chose
A custom OOXML + ZIP/CRC-32 encoder (~zero dependencies)
Over
Bundling SheetJS (~1MB) into the extension

Extensions live and die by review friction and attack surface. Writing the OOXML subset I actually needed (styled sheets, frozen panes, column widths) cost a few days and bought a dependency-free build, a tiny bundle, and no supply-chain exposure. Extension security reviews read fast when there’s nothing to review.

Decision 03

Graceful degradation as architecture

Chose
Isolated audit modules returning independent sheets
Over
One sequential audit pass

HubSpot portals vary in permissions, features, and data volume. Module isolation means a portal without forms access still gets a full workflow audit. Partial value beats a stack trace.

Decision 04

One codebase, two products

Chose
Build-time variant assembly (internal + gated external)
Over
Forked repos or a feature-flag service

The build script copies one source tree and injects config: the internal build for client engagements, the external build with an email gate as a lead magnet. Same diagnostics, two go-to-market motions, zero drift.

// stage.production.notes

What broke, and what it taught the system

Unstable sort orders corrupt paginated counts silently

HubSpot’s search endpoint returns unstable ordering across pages under default sort. Rows appeared twice or never, and workflow counts drifted per run. Fix: explicit stable sort key plus a dedupe pass keyed on object ID. Counts became reproducible, which is the property an audit lives or dies on.

Re-entrancy guards for one-click tools

Users double-click. Two concurrent audit runs in one tab interleaved their paging state and produced a garbage workbook. A per-tab run lock made the second click a no-op with a visible "audit in progress" state.

A validation harness for a tool with no backend

With everything client-side, correctness testing runs against synthetic portal fixtures: known workflow sets with planted duplicates, conflicts, and dormant automations. The diagnostics and the XLSX encoder are tested the same way: byte-level checks that the generated workbook opens clean in Excel.

OUTCOMES

What the numbers say

1-clickfull portal audit from a logged-in session
~230/530/590workflows / lists / properties in a reference audit
0runtime dependencies
2variants from one codebase: internal + lead magnet

Internally, the engine compresses a multi-day audit engagement into minutes, with a client-ready workbook as the deliverable. Externally, the gated variant turns the same capability into a lead magnet: prospects see their own automation debt before any sales conversation happens.

Reverse-engineering an internal API, hand-writing the XLSX encoder, and shipping with zero runtime dependencies were each trade-offs with real costs; the decisions above record the reasoning.

WHAT I’D DO DIFFERENTLY

The honest retro

  • I should have built the validation harness before the diagnostics, not after the first bad workbook. Client-side tools without backends need their test infrastructure earliest, not last.
  • The internal-API dependency needs a canary: a scheduled check that detects response-shape drift before a user does. It’s on the roadmap; it should have shipped with v1.

This is slick. Great work. Can I possibly get the deep dive to use as evidence for an upcoming internal improvement meeting?

Client Stakeholder, Enterprise Financial Reporting Software

// stage.common.questions

What does the HubSpot audit tool check?

Workflows, lists, properties, and forms in one pass: on/off/dormant/never-enrolled classification, error states, near-duplicate workflow clusters, and property-write conflicts. A reference audit covered about 230 workflows, 530 lists, and 590 properties in one click.

Does the audit send my HubSpot data anywhere?

No. The extension runs inside your logged-in HubSpot browser tab and generates the Excel workbook entirely client-side. The only network call outside HubSpot is the optional email gate in the public variant.

Why not use the official HubSpot API?

The public Automation API omits enrollment metrics, error states, and step logic, which are the data an audit actually needs. The extension reads HubSpot’s internal CRM search endpoint from within your session, the same data source the HubSpot UI itself uses.