Skip to content

Campaign Studio — Improvement & Growth Plan

Package: capell-app/campaign-studio · Kind: package · Tier: premium · Product group: Capell Growth · Bundle: growth · Status: Complete

Campaign Studio is a schema-owning growth package (surfaces admin + frontend) that adds campaign groups, landing-page variants, UTM attribution, CTA/hero/lead-form widgets, conversion goals, and funnel/overview reporting on top of Capell. It owns five tables (campaign_groups, campaign_landing_pages, campaign_cta_widgets, campaign_conversion_goals, campaign_conversions) and five models, drives four Filament resources plus three dashboard contributions, and exposes 19 Actions (recording, attribution, variant resolution, URL building, stats, experiment sync/results, public conversion capture, tracker script loading). It hard-requires admin, core, form-builder, frontend, insights, layout-builder and softly supports experiments + seo-suite/site-discovery. Conversion capture flows through the FormBuilder FormSubmitted listener (src/Listeners/RecordFormSubmissionConversion.php) plus the public Campaign Studio beacon for CTA-click and page-view goals.

Marketplace and Composer copy now use buyer-facing campaign/outcome positioning. Screenshots in capell.json marketplace block now include the extension card plus six committed product screenshots for campaign groups, landing-page variants, conversion goals, CTA widgets, dashboard widgets, and the frontend landing page.

  • 2026-06-03: Rewrote marketplace/Composer copy, promoted real product screenshots into the manifest, removed the dead AttributionModel enum, and hardened the overview conversion-rate join against blank utm_campaign values.
  • 2026-06-04: Added UTM fields to the campaign hero widget configurator and routed hero primary/secondary button URLs through BuildCampaignUrlAction, with render coverage proving decorated public URLs do not leak numeric campaign identifiers.
  • 2026-06-04: Added the public campaign conversion beacon, post-load tracker script, typed capture Action/Data boundary, full-page public-output safety tests, and frontend cache contribution metadata proving Campaign Studio output is non-cacheable and varies by UTM targeting dimensions.
  • 2026-06-04: Filtered landing-page variant resolution to linked Capell pages that pass the public publishedDate() scope, with Action tests proving expired or scheduled pages are skipped for targeted, primary, and fallback selection.
  • 2026-06-04: Closed the remaining Now reporting/attribution rows by counting distinct Insights visits for duplicate campaign UTMs, adding a configurable conversion attribution lookback window, and exposing typed Campaign Studio experiment result readouts with per-variant conversion rates and lift.
  • 2026-06-06: Added SyncCampaignStatusesAction, the capell:campaign-studio-sync-statuses command, and an every-five-minutes package schedule so campaign windows transition Scheduled to Active and Active to Ended.
  • 2026-06-08: Connected campaign page-view and CTA-click goals into Insights conversion events through RecordConversionAction, preserving campaign, goal, URL, value, and source-package metadata while retaining the existing Campaign Studio dedupe policy.
  • Shipped 2026-06-04: Wire CTA-click conversions to a real capture path. POST /capell/campaigns/conversions now accepts same-origin page-view and CTA-click beacon posts, resolves Insights visits, landing pages, CTA widgets, and conversion goals through CaptureCampaignConversionAction, and records conversions through the existing campaign conversion Actions. — restores advertised capability — src/Actions/CaptureCampaignConversionAction.php, src/Http/Controllers/CampaignConversionBeaconController.php, src/Support/RenderHooks/RegisterCampaignTrackerHook.php — L
  • Shipped 2026-06-03: Resolve or remove the AttributionModel enum. AttributionModel was removed after review confirmed it had no production consumers and BuildConversionAttributionAction records both first- and last-touch fields. — removes misleading API + clarifies attribution semantics — src/Actions/BuildConversionAttributionAction.php — M
  • Shipped 2026-06-04: Make BuildCampaignOverviewStatsAction conversion-rate join robust. The overview visit denominator now excludes null/blank campaign UTMs and counts distinct Insights visit ids, so duplicate campaign groups sharing a utm_campaign no longer double-count the same visit in the headline conversion-rate KPI. — correctness of a headline KPI — src/Actions/BuildCampaignOverviewStatsAction.php — M
  • Shipped 2026-06-04: Variant resolution ignores active/published state of the target page. ResolveCampaignLandingPageVariantAction now filters all targeted, primary, and first-available candidates through the linked page’s public publishedDate() scope, so scheduled or expired pages are skipped instead of being selected as campaign variants. — prevents serving unpublished content — src/Actions/ResolveCampaignLandingPageVariantAction.php — M
  • Eager-load to avoid N+1 in funnel + landing-page queries. BuildCampaignConversionFunnelAction is clean (withCount), but CampaignLandingPagePublicUrlContributor::publicUrls() loads all landing pages with nested page.pageUrls.* and then unique()s in PHP — fine for sitemaps but unbounded as campaigns grow. Add chunking/pagination for large sites. — sitemap-build scalability — src/Support/PublicUrls/CampaignLandingPagePublicUrlContributor.php — S
  • Shipped 2026-06-04: Hero button URLs bypass BuildCampaignUrlAction. Hero primary/secondary CTAs now use configured widget UTM metadata and BuildCampaignUrlAction to append missing UTM parameters while preserving existing query strings and fragments. The current lead-form widget has no button URL; its capture path remains Form Builder submission attribution. — consistent attribution across hero and CTA widget links — resources/views/components/widget/campaign-hero.blade.php — S
  • Shipped 2026-06-03: Surface the committed screenshots in the manifest. The marketplace manifest now references six committed product screenshots in addition to the extension card. — marketplace listing quality — capell.json — S
  • Populate the changelog. CHANGELOG.md has a single placeholder line (“Prepared package metadata…”). For a premium first-party package this should track schema/Action changes. — release hygiene / buyer trust — CHANGELOG.md — S

Mapped against declared capabilities[] (campaign-audience-targeting, campaign-conversion-funnel-reporting, landing-page-variant-experiments, campaign-experiment-sync, …) and standard campaign-marketing norms.

  • Shipped 2026-06-04: Client-side conversion capture (table stakes). Campaign Studio now injects a post-load tracker through the frontend BodyEnd render hook and records page-view plus CTA-click conversions through the same-origin Campaign Studio beacon. — table stakes.
  • Audience targeting beyond UTM (differentiator vs gap). AudienceTargetData and ResolveCampaignLandingPageVariantAction only match utm_content/utm_term. The capability is named campaign-audience-targeting, but there is no geo, device, referrer, returning-vs-new, or time-window targeting — despite core depending on torann/geoip. Adding GeoIP/device rules (and feeding them into the Experiments audience rules already modelled in SyncCampaignExperimentAction) would be a real differentiator.
  • Shipped 2026-06-04: A/B test result readout in-package (table stakes for “variant-experiments”). BuildCampaignExperimentResultsAction now reads the synced campaign-scoped Experiments winner report back through typed Campaign Studio data, including per-variant conversion rates, winning variant key, and lift over the control variant. — table stakes.
  • Shipped 2026-06-06: Scheduling automation. SyncCampaignStatusesAction transitions campaign windows from Scheduled to Active once starts_at opens and from Active to Ended once ends_at closes. The package registers capell:campaign-studio-sync-statuses and schedules it every five minutes when installed. — table stakes for “campaign scheduling”.
  • Shipped 2026-06-08: Insights conversion loop. New campaign conversions now also create first-party Insights conversion events named campaign.{campaign_slug}.{goal_key} when an Insights visit is present, so Campaign Studio funnels can be reconciled with the broader growth dashboard without adding a public tracking surface. — table stakes for the Growth bundle.
  • Multichannel / channel taxonomy. Attribution is UTM-only. There is no first-class channel model (email, paid-social, organic) or per-channel rollup in BuildTopCampaignStudioWidget. Marketing buyers expect channel breakdowns. — differentiator.
  • Revenue / value attribution. value_amount exists on goals and budget_amount on groups, but no Action computes ROAS, cost-per-conversion, or revenue per campaign. The funnel only counts conversions. Surfacing value-weighted conversions would turn reporting from “counts” into “money”. — differentiator.
  • Shipped 2026-06-04: Configurable attribution lookback window. RecordCampaignConversionAction now honors capell-campaign-studio.attribution.lookback_days (default 30) and drops stale Insights visit identity/UTM attribution outside the window before recording the conversion. — table stakes.
  • Anonymous/no-identity conversion dedup policy. Conversions with no visit/event/source still skip dedup (hasIdentity() returns false → plain create). A future row should decide whether anonymous conversions need a bounded dedupe policy by goal, landing page, and time window without collapsing separate anonymous visitors into one conversion. — table stakes.
  • Public landing-page route ownership. The package contributes URLs to Site Discovery but renders through core’s capell.pages.show. There is no campaign-native short URL / vanity slug (/go/{campaign}) that auto-applies UTMs and redirects — a common campaign-tool feature. — differentiator.
  • Shipped 2026-06-04: Cache-vs-personalization contradiction. Campaign Studio now records a non-cacheable frontend render contribution with UTM variance metadata whenever its tracker is injected, and docs clarify that conversion capture is post-load/cache-compatible while UTM-targeted variant selection remains dynamic frontend output. Tests assert the tracker contribution is non-cacheable and carries utm_campaign, utm_content, and utm_term. — capell.json (performance block), src/Support/RenderHooks/RegisterCampaignTrackerHook.phpHigh.
  • Shipped 2026-06-03: Dead enum shipped in a premium API. AttributionModel was removed after review confirmed it had no production consumers and BuildConversionAttributionAction always records both first- and last-touch fields. — Medium.
  • Shipped 2026-06-04: CTA/page-view Actions are untested in integration and unreachable in production. Feature tests now cover route registration, page-view capture, CTA-click capture, invalid-origin rejection, and deduping through Insights visit identity/source context. — Medium.
  • Shipped 2026-06-04: No frontend render/feature test. Feature coverage now renders a full public campaign page fragment with widget output plus the tracker hook and asserts no authoring markers, model fields, numeric campaign id attributes, or signed editor URLs leak. — Medium.
  • Public-output safety: currently OK, keep it that way. tracking/attributes.blade.php and campaign-cta-widget.blade.php expose only campaignGroup->slug and goal/cta key — no numeric IDs, field paths, or model names — and a test guards this. The hero/lead-form widgets render data-campaign-goal from getMeta('goal_key') (admin-authored string), which is acceptable. Risk is regression if a future change emits IDs; lock with a render-level (not just component-level) assertion. — resources/views/components/** — Low (but easy to regress).
  • SyncCampaignExperimentAction uses forceFill(...)->save() and direct relation writes. It bypasses the Experiments package’s own create Action on the update path (only the create path calls CreateExperimentAction). If Experiments adds validation/events to variant/goal writes, this drifts. Cross-package write coupling is a maintenance risk. — src/Actions/SyncCampaignExperimentAction.php L48-65, L199-250 — Medium.
  • Shipped 2026-06-04: utm_campaign join double-counted duplicate campaigns. Multiple campaign groups can still share a utm_campaign (only slug is unique), but the overview/visit join now counts distinct Insights visits so duplicate groups do not inflate the denominator. Shared UTM values remain analytically ambiguous and should be avoided for precise campaign attribution. — src/Actions/BuildCampaignOverviewStatsAction.php — Medium.
  • i18n completeness. Enum labels and Filament strings are translated via __() (good). Verify all five lang files (form, generic, navigation, package, widgets) carry every key the code references (e.g. generic.landing_page_variant, widgets.active_campaign-studio); the odd active_campaign-studio array key (hyphen in an array key/translation slug) is fragile. — resources/lang/en/*, src/Actions/BuildCampaignOverviewStatsAction.php — Low.
  • Performance budget unverified. adminQueryBudget: 40 and frontendRenderBudgetMs: 20 are declared but no test or benchmark asserts them; the CTA hydrate batch (CampaignCtaWidget::hydrateWidgets) is well-designed for it, but unmeasured. — capell.json — Low.

The manifest and Composer description now use this buyer-facing one-sentence summary:

Launch, target, and measure marketing campaigns inside Capell — build landing-page variants, drop in CTA and lead-capture widgets, and track UTM-attributed conversions and funnels without bolting on a separate analytics tool.

The package should continue toward this fuller buyer-facing product story as the remaining conversion-capture gaps land:

Campaign Studio turns Capell into a campaign command centre for marketing and growth teams. Group your landing pages, CTAs, and lead forms under a campaign, target visitors with UTM-driven page variants, and let conversions flow in automatically from form submissions and on-page goals. Built-in funnel and overview reporting show which campaigns and pages actually convert — and how that rate trends week over week — without exporting to a third-party tool. When the Experiments add-on is installed, your landing-page variants and conversion goals sync straight into a running A/B test.

Screenshot / media gaps. Manifest now exposes the extension card plus six real product screenshots. Missing entirely: a hero showing the funnel/overview stats, and an animated GIF of variant selection or the experiment sync. Dark captures exist in docs/screenshots/ but are not yet promoted into marketplace media; decide whether to add them or keep the listing focused on the light captures.

Pricing / tier / bundle positioning. Premium tier in the growth bundle is right — this is a paid, first-party, priority-support package. It is the natural anchor of the Capell Growth bundle: it hard-requires insights (analytics), form-builder (lead capture), and layout-builder (widgets), so selling Campaign Studio pulls those in. Cross-sell levers: (1) experiments add-on as an upsell unlocked by the existing SyncCampaignExperimentAction; (2) seo-suite / site-discovery for sitemap + AI-discovery of landing pages (already wired via CampaignLandingPagePublicUrlContributor); (3) bundle with a future “Automation Studio” that listens to the already-dispatched CampaignConverted event. Position as the Extension Suite centrepiece: “Campaign Studio + Experiments + SEO Suite = the Capell Growth Suite.”

Differentiators / value props / target buyer. Target buyer: the in-house marketer or agency running campaigns on a Capell-built site who today stitches together GA + a form tool + spreadsheets. Value props: campaigns, variants, widgets, and conversion reporting native to the CMS (no tag-manager glue); UTM attribution captured server-side and deduped; one-click sync to real A/B experiments; landing pages that automatically enter the sitemap and AI-discovery feeds. Differentiator to lean into: closed-loop — the same campaign that builds the page also reports its conversions and feeds the experiment.

Keywords / tags (8–12): campaign management, landing pages, conversion tracking, UTM attribution, A/B testing, lead capture, marketing CMS, CTA widgets, conversion funnel, growth marketing, audience targeting, Filament.

Completed 2026-06-08. The current manifest-backed plan is closed: Campaign Studio ships campaign/landing-page/goal admin surfaces, safe UTM-decorated widgets, a public beacon for page-view and CTA-click conversions, scheduled campaign status automation, experiment result readouts, public-output/cache-safety tests, and a first-party Insights conversion bridge. Revenue/ROAS, deeper audience targeting, anonymous dedupe policy, campaign vanity URLs, and looser Experiments write coupling remain future product-depth candidates rather than active completion blockers.

ItemBucketEffortImpactSection ref
Shipped 2026-06-04: Wire CTA-click + page-view conversion capture (beacon)DoneLHigh§2, §3
Shipped 2026-06-04: Add frontend render/feature tests proving widgets render + no ID/marker leak in full pageDoneMHigh§4
Shipped 2026-06-04: Resolve the cacheable=false vs static-HTML-cache personalization contradiction (tracker contribution + doc)DoneMHigh§4
Shipped 2026-06-04: Filter variant resolution to published pagesDoneMMedium§2
Shipped 2026-06-04: Harden overview conversion-rate join (null/duplicate utm_campaign)DoneMMedium§2, §4
Shipped 2026-06-06: Add campaign scheduling command (Scheduled→Active→Ended transitions)DoneMMedium§3
Shipped 2026-06-04: In-package A/B variant results readout (lift per variant)DoneLHigh§3
Shipped 2026-06-08: Feed Campaign Studio conversions into Insights conversion eventsDoneMHigh§3, §5
Revenue/ROAS reporting using existing value_amount + budget_amountFutureMHigh§3
Geo/device/referrer audience targeting (use torann/geoip)FutureLHigh§3
Shipped 2026-06-04: Configurable attribution lookback windowDoneMMedium§3, §4
Anonymous/no-identity conversion dedup policyFutureMMedium§3, §4
Decouple SyncCampaignExperimentAction update path from direct relation writesFutureMLow§4
Populate CHANGELOG + benchmark perf budgets (20ms render / 40 query)FutureSLow§2, §4