Abuja Digital Studio · Est. 2018
Start a Project
Docs
OAds
Native ad manager for WordPress — run image, text-card, and HTML ads with section targeting, date scheduling, impression & click tracking, and a full analytics dashboard.
oads
Developer documentation

OAds v4.0.1

Native ad manager for WordPress. Serve direct ads without sharing revenue with ad networks. Six ad types, full targeting, A/B rotation, impression and click tracking, and revenue analytics.

WordPress plugin WP 6.0 · PHP 8.0+ Direct ads only v4.0.1
01 · Overview

What OAds does

OAds is a native ad manager for direct ad sales. It manages ads you sell directly to advertisers and keeps 100% of the revenue.

CapabilityDetail
Ad types6 types: image banner, text card, HTML embed, video, sponsored content card, sticky bar
TargetingDevice, user role, category, country (geo-IP), frequency cap, date scheduling
RotationAd groups with random (weighted), round-robin, and A/B split-test rotation
AnalyticsImpressions, clicks, CTR, unique IPs, estimated revenue (CPM + CPC)
ZonesNamed placement areas with dimensions, fill priority, and AdSense fallback
PrivacyGDPR consent gate, frequency capping via localStorage, ad disclosure label
PlacementShortcode, PHP template functions, WP action hook, WP widget, Gutenberg block, Elementor widget, auto-injection
Advertiser portalSelf-serve submission form, advertiser dashboard, admin approve/reject queue, WooCommerce wiring
Sellable plansPre-priced packages with impression caps, allowed sections and ad types, WooCommerce auto-activation
Header biddingOptional Prebid.js integration with self-hosted build, per-bidder config, price floor, timeout
SurfacesREST API (oads/v1), full WP-CLI command surface, multisite network defaults
Admin UIOrravo design system: dark/light, sticky header, matches OMailer/OForum
02 · Installation

Getting installed

1
Upload the oads/ folder to wp-content/plugins/.
2
Activate via Plugins → Installed Plugins.
3
On activation: creates 7 database tables, registers the oads_ad custom post type, adds oads_settings option with defaults, registers the click-tracking rewrite rule (/oads-click/{id}/), and flushes rewrite rules.
4
Navigate to OAds in the WP admin menu.
03 · Admin Interface

Admin interface

Follows the exact Orravo design system, with a sticky 2-row header positioned below the WP admin bar at top: 32px. Theme preference stored in localStorage under oads_theme.

TabDescription
Manage AdsCreate, edit, pause, duplicate, bulk-edit, delete, preview ads
AnalyticsImpressions/clicks/CTR/revenue charts, video event funnel, per-ad table, section matrix
ZonesDefine named placement zones with dimensions, fill priority, AdSense fallback, prebid sizes
Ad GroupsSet up rotation pools and A/B split tests
PortalReview advertiser submissions, approve to publish, reject with reason
PlansManage sellable plans (price, impression cap, duration, allowed sections, WooCommerce wiring)
Tracking LogRaw impression and click log with CSV export
SettingsGDPR, disclosure label, frequency cap, lazy delivery, AdSense fallback, prebid header bidding, data retention, REST API key
04 · Ad Types

Six ad types

🖼️
Image + Link
type: image
Standard banner. WP media library upload. Optional overlay headline. Click-tracked via redirect.
📝
Text Card
type: text
Headline + body copy + CTA button. Contextually relevant in article feeds.
💻
HTML Embed
type: html
Raw HTML or third-party script, passed through wp_kses_post. Use for rich media or custom units.
🎬
Video
type: video
Self-hosted MP4 (autoplay muted) or YouTube nocookie embed. IntersectionObserver starts/stops on scroll.
📰
Sponsored Content Card
type: sponsored
Looks like an editorial post card. Image + headline + description + CTA. Blends with content feed listings.
📌
Sticky Bar
type: sticky
Fixed top or bottom bar. Persists while user scrolls. Close (✕) button with smooth dismiss animation.

Ad meta keys

Meta keyUsed byDescription
_oads_image_urlimageImage URL
_oads_image_idimageWP attachment ID
_oads_text_headlineimage, textHeadline / overlay headline
_oads_text_bodytextBody copy
_oads_text_ctatextCTA button label (default: "Learn More")
_oads_destination_urlimage, textClick destination URL
_oads_html_embedhtmlRaw HTML content
_oads_video_urlvideoMP4 URL or YouTube URL
_oads_video_typevideoself or youtube
_oads_sponsored_headlinesponsoredCard headline
_oads_sponsored_descsponsoredCard description
_oads_sponsored_ctasponsoredCTA label
_oads_sponsored_img_urlsponsoredCard image URL
_oads_sticky_positionstickytop or bottom
05 · Targeting System

Targeting system

Targeting is evaluated server-side in OAds_CPT::passes_targeting() before an ad is served. Six independent targeting dimensions, all optional and additive.

Device User role Category Country / Geo Frequency cap Date schedule

Device targeting (_oads_target_device)

ValueBehaviour
bothAll devices (default)
mobileMobile only (uses wp_is_mobile())
desktopDesktop only

User role targeting (_oads_target_roles)

Comma-separated role slugs. Special values: logged_in (any authenticated user), logged_out (unauthenticated visitors), or any WP role slug such as subscriber, editor. Example: logged_in, subscriber

Category targeting (_oads_target_categories)

Comma-separated category IDs. Ad shows only when the current page belongs to one of those categories. Works on singular posts and category archive pages. Example: 3, 7, 12

Country targeting (_oads_target_countries)

Comma-separated ISO 3166-1 alpha-2 codes. Requires a geo-IP lookup at the theme level. OAds reads the constant or option OADS_VISITOR_COUNTRY. Leave blank to show to all countries.

Frequency cap (_oads_freq_cap)

Maximum impressions per user per day. Implemented in JavaScript using localStorage (key: oads_fc_{ad_id}). Set to 0 to disable.

Date scheduling

Meta keyFormatDescription
_oads_start_dateYYYY-MM-DDAd won't show before this date
_oads_end_dateYYYY-MM-DDAd won't show after this date
06 · Placement & Shortcodes

Placement & shortcodes

Shortcodes

OAds registers four shortcodes covering ad delivery, the advertiser self-serve portal, the advertiser dashboard, and the sellable plans grid.

SHORTCODE[oads]
[oads section="blog" count="2"]
[oads type="inline" section="shop"]
[oads zone="sidebar"]
[oads placement="sidebar" section="global" count="1"]

[oads_portal]              // advertiser submission form
[oads_portal_dashboard]    // logged-in advertiser dashboard (their submissions + status)
[oads_plans]               // sellable plans pricing grid
ShortcodeUse
[oads]Render an ad in any post, page, widget, or block.
[oads_portal]Drop on a sales page so advertisers can submit creative, sections, budget, and dates. Submissions land in wp_oads_submissions for admin review.
[oads_portal_dashboard]Logged-in advertiser dashboard listing their own submissions and statuses.
[oads_plans]Renders a pricing grid from wp_oads_plans. Wires WooCommerce product IDs (auto-activates the plan on order completion) or routes to a custom CTA URL.

[oads] attributes

AttributeDefaultOptions
sectionglobalAny section slug or zone slug
count1Integer (capped at 5)
typecardcard, inline
zone(none)Named zone slug. When set, uses the zone's AdSense fallback if no direct ads are eligible.
lazysetting'0' / '1'. Inherits the global lazy_load setting.

PHP template functions

PHP// Show 1 card ad in blog section
oads_show( 'blog', 1 );

// Show 2 inline ads
oads_show_inline( 'global', 2 );

// Get raw HTML
$html = oads_get( 'homepage', 1 );

// Inject an ad every 6 items in a WP_Query loop
foreach ( $items as $i => $item ) {
    // … render item …
    oads_inject_in_loop( 'blog', 6, $i );
}

// Render a named zone
oads_zone( 'sidebar-top', 'card' );

WP action hook

PHPdo_action( 'oads_zone', 'blog', 'inline' );

Automatic injection

OAds auto-injects ads without any template code, and respects a cap of 3 ads per page. Ads in header and footer elements are automatically hidden via a <style> injection.

ContextHow ads are injected
Singular postsAfter paragraph 3 and paragraph 7 (if post is long enough)
Archive / listing pagesCard ads inserted into the largest CSS Grid on the page, every N items
Sticky bar adsInjected into <body> via wp_footer
07 · Ad Groups & Rotation

Ad groups & rotation

Pool multiple ads together and serve them according to a rotation policy. Set the Ad Group field on each ad to assign it to a pool.

Random (Weighted)
Each ad drawn randomly, weighted by its _oads_weight value (1–10). A weight of 3 is 3× more likely than weight 1.
Round-Robin
Each ad takes a turn in strict sequence. Pointer stored in the oads_group_rr_pointers option.
A/B Split
Weighted random until any variant hits 100+ impressions. maybe_pick_winner() then compares CTR and locks in the winner exclusively.

Setting up a group

1
Go to OAds → Ad Groups → New Group and set the rotation type.
2
Edit each ad and set the Ad Group field to this group.
3
Set Weight per ad (1–10) for weighted or A/B rotation.
4
Use [oads] normally. The group's rotation logic picks which ad to serve.
08 · Ad Zones

Ad zones

Named, configurable placement areas. Instead of hardcoding section names in templates, zones let you define dimensions, fill priority, and an AdSense fallback per zone.

FieldDescription
NameHuman-readable label
SlugUsed in shortcode: [oads zone="slug"]
Max Width / HeightInformational dimensions constraint
Fill Prioritydirect (sold ads) → house (promotional) → remnant (fallback)
AdSense Fallback CodeShown when no direct ad fills the zone
SHORTCODE[oads zone="sidebar-top"]
PHPoads_zone( 'sidebar-top', 'card' );
// or via action hook:
do_action( 'oads_zone', 'sidebar-top', 'inline' );
09 · Analytics & Revenue Tracking

Analytics & revenue

Dashboard metrics

MetricHow calculated
ImpressionsUnique page views where an ad was observed (IntersectionObserver ≥ 50% threshold)
ClicksTracked via server-side redirect through /oads-click/{id}/
CTR(clicks / impressions) × 100
Unique IPsDistinct visitor IPs in the selected period
Est. RevenueCPM + CPC rates set per ad (see formula below)

Revenue formula

CPM revenue
revenue += (impressions ÷ 1,000) × cpm_rate

CPC revenue
revenue += clicks × cpc_rate

Total (per ad)
total_revenue = cpm_revenue + cpc_revenue

Set CPM and/or CPC rates per ad in the ad editor. Total revenue is the sum across all ads for the selected period.

Time periods

KeyRange
7dLast 7 days
30dLast 30 days
90dLast 90 days
allSince 2020-01-01

CSV export

Click Export Impressions CSV or Export Clicks CSV on the Analytics or Log tab. AJAX action: oads_export_csv. Parameters: type (impressions|clicks), period (7d|30d|90d|all).

10 · WP Widget

WP widget

OAds registers a WP Widget, OAds Ad Zone, for placing ads in sidebar widget areas.

FieldDefaultDescription
Title(blank)Widget title shown above ads
SectionsidebarWhich ad section to pull from
Number of ads11–5
Formatcardcard or inline
11 · Privacy & GDPR

Privacy & GDPR

Consent gate

Enable Settings → Privacy & GDPR → Require consent before tracking. The frontend JS checks localStorage.getItem('oads_consent') === '1' before firing any impression or click tracking. If consent is absent, the ad still displays but no data is recorded.

Your CMP (cookie banner) should set this when the user consents:

JSlocalStorage.setItem('oads_consent', '1');

Data stored

TableData recorded
wp_oads_impressionsad_id, page_url, section, visitor_ip, user_agent, timestamp
wp_oads_clicksad_id, page_url, section, visitor_ip, user_agent, referrer, timestamp
ℹ️IP addresses are stored as-is (IPv4/IPv6). For GDPR IP anonymization, hash the IP before storage by hooking the oads_before_impression action or by forking the tracker class.

Ad disclosure label

Every ad carries a disclosure label (default: Sponsored). Override per-ad in the editor. Ensures compliance with FTC guidelines and similar requirements.

12 · Database Tables

Database schema

7 custom tables created on activation, all properly indexed for the common queries (per-ad, per-section, per-day rollups).

wp_oads_impressions

SQLid          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
ad_id       BIGINT UNSIGNED NOT NULL
page_url    VARCHAR(500)
section     VARCHAR(100)
visitor_ip  VARCHAR(45)
user_agent  VARCHAR(500)
created_at  DATETIME DEFAULT CURRENT_TIMESTAMP

INDEX idx_ad_id (ad_id)
INDEX idx_created (created_at)
INDEX idx_section (section)

wp_oads_clicks

SQLid          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
ad_id       BIGINT UNSIGNED NOT NULL
page_url    VARCHAR(500)
section     VARCHAR(100)
visitor_ip  VARCHAR(45)
user_agent  VARCHAR(500)
referrer    VARCHAR(500)
created_at  DATETIME DEFAULT CURRENT_TIMESTAMP

INDEX idx_ad_id (ad_id)
INDEX idx_created (created_at)

wp_oads_zones

SQLid              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
name            VARCHAR(100)
slug            VARCHAR(100) UNIQUE
max_width       INT UNSIGNED
max_height      INT UNSIGNED
fill_priority   ENUM('direct','house','remnant')
adsense_code    TEXT
prebid_enabled  TINYINT(1) DEFAULT 0
prebid_sizes    VARCHAR(200) DEFAULT '300x250'
created_at      DATETIME DEFAULT CURRENT_TIMESTAMP

wp_oads_groups

SQLid              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
name            VARCHAR(100)
rotation_type   ENUM('random','roundrobin','ab')
ab_winner       BIGINT UNSIGNED DEFAULT 0
created_at      DATETIME DEFAULT CURRENT_TIMESTAMP

wp_oads_video_events

SQLid          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
ad_id       BIGINT UNSIGNED NOT NULL
event_type  VARCHAR(20)   /* start, q1, mid, q3, end */
visitor_ip  VARCHAR(45)
created_at  DATETIME DEFAULT CURRENT_TIMESTAMP

INDEX idx_ad_id (ad_id)
INDEX idx_event (event_type)
INDEX idx_created (created_at)

wp_oads_submissions

SQLid              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
name            VARCHAR(200)
email           VARCHAR(200)
company         VARCHAR(200)
website         VARCHAR(500)
ad_type         VARCHAR(50)
section         VARCHAR(100)
headline        VARCHAR(300)
body            TEXT
destination_url VARCHAR(500)
image_url       VARCHAR(500)
budget          DECIMAL(10,2)
preferred_start DATE
notes           TEXT
status          VARCHAR(20) DEFAULT 'pending'
post_id         BIGINT UNSIGNED DEFAULT 0
reject_reason   TEXT
created_at      DATETIME DEFAULT CURRENT_TIMESTAMP

INDEX idx_email (email)
INDEX idx_status (status)
INDEX idx_created (created_at)

wp_oads_plans

SQLid              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
name            VARCHAR(200)
description     TEXT
price           DECIMAL(10,2)
impressions     BIGINT UNSIGNED
duration_days   INT UNSIGNED DEFAULT 30
ad_types        VARCHAR(200)
sections        VARCHAR(200)
features        TEXT
cta_label       VARCHAR(100) DEFAULT 'Get Started'
cta_url         VARCHAR(500)
wc_product_id   BIGINT UNSIGNED DEFAULT 0
is_featured     TINYINT(1) DEFAULT 0
active          TINYINT(1) DEFAULT 1
created_at      DATETIME DEFAULT CURRENT_TIMESTAMP
13 · PHP API

PHP API reference

OAds_CPT

PHP// Get active ads for a section
OAds_CPT::get_ads( string $section, int $limit, string $placement, array $ctx ): array

// Get all meta for an ad
OAds_CPT::get_ad_data( int $ad_id ): array

// Targeting check: returns false if ad should not be shown
OAds_CPT::passes_targeting( int $ad_id, array $ctx ): bool

OAds_Display

PHPOAds_Display::get_ads_html( string $section, int $count ): string
OAds_Display::get_inline_ads_html( string $section, int $count ): string
OAds_Display::render_ad( WP_Post $ad, string $section, string $format ): string
OAds_Display::detect_section(): string

OAds_Tracker

PHPOAds_Tracker::record_impression( int $ad_id, string $page_url, string $section ): void
OAds_Tracker::record_click( int $ad_id ): void

// Per-ad stats: returns [ impressions, clicks, ctr, revenue ]
OAds_Tracker::get_ad_stats( int $ad_id, string $period ): array

// Overview stats for all ads
OAds_Tracker::get_overview_stats( string $period ): array

// Raw log entries
OAds_Tracker::get_recent_log( int $limit, string $type ): array

// CSV export (exits)
OAds_Tracker::export_csv( string $type, string $period ): never

OAds_Zones

PHPOAds_Zones::get_all(): array
OAds_Zones::get_by_id( int $id ): ?object
OAds_Zones::get_by_slug( string $slug ): ?object
OAds_Zones::save( array $data ): int|false
OAds_Zones::delete( int $id ): bool

OAds_Groups

PHPOAds_Groups::get_all(): array
OAds_Groups::get_by_id( int $id ): ?object
OAds_Groups::get_ads_in_group( int $group_id ): array
OAds_Groups::pick_ad( int $group_id ): ?WP_Post
OAds_Groups::maybe_pick_winner( int $group_id ): void
OAds_Groups::save( array $data ): int|false
OAds_Groups::delete( int $id ): bool
14 · Hooks & Filters

Hooks & filters

Actions

HookArgsDescription
oads_zonestring $zone_slug, string $formatRender an ad zone. Call via do_action('oads_zone', 'sidebar', 'card') or use the oads_zone() helper.
oads_before_impressionint $ad_id, string $page_url, string $sectionFires immediately before an impression row is inserted. Hook to short-circuit, anonymize, or fan out to your own analytics.
oads_after_clickint $ad_id, string $destination_urlFires after a click is recorded, immediately before the 302 redirect.
oads_portal_submission_receivedarray $rowFires after a new advertiser submission row is inserted via the portal.
oads_submission_approvedint $ad_id, array $subFires when an admin approves a submission and a corresponding oads_ad post is created.
oads_submission_rejectedint $submission_id, array $subFires when an admin rejects a submission.
oads_data_retention(none)Weekly cron event that calls OAds_Tracker::purge_old_data(). Replace with your own callback to customize purge logic.

Filters

FilterArgsDescription
oads_candidate_adsWP_Post[] $ads, string $section, array $contextModify the candidate pool of ads after targeting and yield ranking, before delivery.
oads_targeting_resultbool $passes, int $ad_id, array $ctxOverride whether a given ad passes targeting for the current request.
oads_render_adstring $html, WP_Post $ad, string $formatOverride or extend the rendered ad HTML for any display format.
oads_settingmixed $value, string $key, mixed $defaultFilter any resolved oads_settings value. Used by the multisite module to apply network-level defaults.
15 · REST API

REST API

All endpoints live under the oads/v1 namespace. Auth accepts a Bearer API key (set api_key in settings) or any logged-in user with manage_options.

Endpoints

MethodPathDescription
GET/oads/v1/stats?period=30dOverview metrics: impressions, clicks, CTR, unique visitors, revenue.
GET/oads/v1/adsList all ads with their meta (up to 100).
GET/oads/v1/ads/{id}/stats?period=30dPer-ad stats for a given period.
GET/oads/v1/ads/{id}/video-stats?period=30dPer-ad video event funnel (start, q1, mid, q3, end).
GET/oads/v1/analytics/section-matrix?period=30dSection-by-ad-type performance matrix.

Period values: 1d, 7d, 30d, 90d, all.

Auth example

SHcurl -H "Authorization: Bearer ${OADS_API_KEY}" \
  https://your-site.com/wp-json/oads/v1/stats?period=7d
16 · Settings Reference

Settings reference

Settings stored in get_option('oads_settings'). Access via helper: oads_setting( 'key', $default ).

KeyTypeDefaultDescription
gdpr_consent'0' / '1''0'Require localStorage consent before tracking
gdpr_gpc'0' / '1''1'Honor the browser Global Privacy Control signal
ccpa_enabled'0' / '1''0'Surface a "Do Not Sell" link for California visitors
ip_anonymize'0' / '1''0'Hash visitor IP before storage (GDPR-friendly)
data_retention_daysint180Weekly cron purges impression and click rows older than this
disclosure_labelstring'Sponsored'Default ad label text shown on every ad
freq_cap_defaultint0Global default frequency cap (0 = off)
lazy_load'0' / '1''1'Defer below-the-fold zone rendering until scrolled into view
adsense_pub_idstring''AdSense publisher ID for zone fallbacks
adsense_slot_idstring''Default AdSense slot ID
api_keystring''Bearer token for the REST API. When set, requests must send Authorization: Bearer {key}.
prebid_enabled'0' / '1''0'Enable header bidding via Prebid.js
prebid_urlstring''Self-hosted Prebid.js URL. Module refuses to load until set, with a clear admin notice.
prebid_timeoutint1500Prebid auction timeout in milliseconds
prebid_price_floorfloat0.50Minimum CPM floor for accepting prebid bids
prebid_biddersJSON string'[]'List of bidder codes participating in auctions
prebid_bidder_configJSON string'[]'Per-bidder parameters keyed by bidder code
portal_notify_emailstringadmin emailAddress that receives advertiser portal submission notifications
17 · Uninstallation

Clean uninstall

Deactivating preserves all data. Deleting the plugin via WP admin runs the uninstall hook and removes everything permanently.

1
Drops wp_oads_impressions
2
Drops wp_oads_clicks
3
Drops wp_oads_zones
4
Drops wp_oads_groups
5
Drops wp_oads_video_events
6
Drops wp_oads_submissions
7
Drops wp_oads_plans
8
Deletes oads_settings and oads_group_rr_pointers options
9
Permanently deletes all oads_ad posts and their post meta
⚠️This is irreversible. Export your analytics CSVs from the Analytics tab before uninstalling if you need historical data.
18 · Competitive Positioning

How OAds stacks up

FeatureOAdsAdvanced AdsAdSanityAd Inserter
Internal ad CPT-
6 ad typespartial--
Impression/click trackingpaid add-on-
Revenue tracking (CPM/CPC)---
A/B split auto-optimizepaid add-on--
Ad group rotation--
Category targetingpaid add-on-
Device targetingpaid add-on-
Role targetingpaid add-on-
Geo targeting hookpaid add-on-
Frequency cappingpaid add-on-paid
Named zones--
Analytics chart-basic-
CSV export---
GDPR consent gatepartial--
WP Widget
Modern admin UI-partial-
Single price, no add-ons--partial
✦ Need help?

Got a question about OAds?

Reach out directly. Kenneth replies within 24 hours.