WP Manifestindependent plugin directory
manifest / content / ad-pilot

AdPilot

Self-hosted advertising inventory, moderated campaigns and progressive delivery.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/ildrm/ad-pilot/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

AdPilot is a self-hosted WordPress advertising plugin for publishers who want to manage advertising inventory, review advertiser creatives, schedule campaigns, and render eligible ads through their own WordPress installation.

The repository includes a publisher console, an authenticated advertiser portal, a visual Layout Studio, native text creative moderation, fixed-price quoting, exclusive inventory holds, and a lightweight public delivery runtime. WordPress provides the user accounts, administration environment, REST infrastructure, and React/editor libraries.

Current version: 0.1.0 — development preview. The plugin is installable, but the complete 54-package implementation program is unfinished. Payment certification, private media uploads, several R1 workflows, and the R2–R4 subsystems remain outstanding. The implementation ledger records those boundaries; the original specifications describe the intended product, including features that are not yet available.

See the 0.1.0 release notes for the packaged features, installation guidance, build verification, and known limitations.

Contents

Current capabilities and boundaries

Area Available in this development build Important boundary
Publisher console Overview, inventory, Layout Studio, campaigns, native creative moderation, reports, settings, and diagnostics The full planned finance and operator workspaces are incomplete.
Advertiser portal Organization selection, placement browsing, native creative drafts/submission, campaign drafts, basic reports, and quote/hold/order flow Requires a WordPress login and current organization membership. Full onboarding, team invitations, invoices, and notifications are incomplete.
Inventory Zones, compact grid containers, stable logical slot IDs, paginated lists, and search Bulk lifecycle controls and complete placement-binding/capacity policies are incomplete.
Layout publication Draft, save, validation, immutable published revisions, and responsive container settings Published container geometry is frozen. Sold-inventory migration, complete impact review, undo/redo, and controlled rollback are not implemented.
Creatives Native text content, destination validation, immutable versions, and reasoned exact-version review Raster upload/quarantine, video, advertiser HTML, and advertiser JavaScript are unavailable. The successor-version API exists; a complete editing wizard does not.
Campaign delivery Separate approval, financial, and delivery states; explicit complimentary allocation; paid-allocation application service; pause, resume, and stop Full lifecycle automation, targeting, pacing, rotation, and allocation policies remain incomplete.
Pricing and reservations Exact fixed-window prices, quote snapshots, expiring idempotent holds, and exclusive interval conflict checks The current tax policy supports only an explicitly recorded not-applicable case. Other tax jurisdictions and pricing models need further implementation.
Commerce Optional WooCommerce CRUD/HPOS adapter and settlement application logic Checkout is unavailable by default. Real gateway, tax, refund, reversal, invoice, and reconciliation certification is outstanding.
WordPress integration PHP helpers, shortcodes, four registered dynamic block names, and an explicit theme action Repeater/showcase currently use the shared renderer; full interval injection, curated showcase behavior, and a dedicated traditional widget adapter are incomplete.
Reporting Scoped counts and basic campaign projections; served facts distinguished from browser render claims Measurement is off by default. Qualified analytics, advanced exports, attribution, and financial aggregates are incomplete. Browser signals never create charges.
Later releases Backend availability reports explain unavailable features R2 heatmaps, R3 metered billing, and R4 replay/federation are unavailable. Outbound webhooks and a complete provider SDK are also unavailable.

The plugin runs without a hosted AdPilot service. Its shipped interface uses WordPress React, system fonts, and scoped styles. No separate React runtime, remote font library, or third-party analytics script is bundled.

Requirements and compatibility

Installed plugin

Component Requirement or current behavior
WordPress The plugin header declares WordPress 6.7 or newer. This minimum is not a certified compatibility matrix.
PHP 8.2 or newer, with WordPress's normal PHP/database requirements and mbstring for text validation.
Database MySQL/MariaDB-backed WordPress storage with InnoDB for all AdPilot tables. Mutations require transactional storage.
Browser JavaScript enabled and modern browser APIs for the publisher console, advertiser portal, and progressive public ad loading. Use HTTPS for deployed interfaces, including browser cryptographic APIs used by booking.
Theme Placement through supported blocks, shortcodes, or explicit PHP/theme integration. A full classic/block-theme compatibility matrix has not been certified.
WooCommerce Optional. Inventory, moderation, complimentary campaigns, and rendering do not require it. Installing it alone does not enable certified checkout.
Multisite Activate separately for each site. Network-wide activation is rejected; federation and network provisioning are not certified.

Python, Composer, Node.js, and npm are not requirements for running the installed plugin. Node/npm are used to rebuild frontend assets from source. PHP's zip extension is required by the repository's packaging command, not by ordinary ad rendering.

Verification status

An earlier disposable environment exercised WordPress 7.1.3, PHP 8.5.8, and MariaDB 13.0.2 on macOS arm64. The responsive interface was reviewed in Chrome at desktop, tablet, and mobile widths. Those results are limited to the paths and environment tested; they do not establish complete minimum-version, browser, theme, assistive-technology, WooCommerce, or multisite support.

That disposable WordPress installation, database, credentials, preview services, and raw generated evidence were removed during repository cleanup. There is no preconfigured local site or default administrator password in this checkout.

Installation

Install a packaged ZIP

  1. Obtain a built adpilot-0.1.0.zip, or build one using the development instructions.
  2. In your WordPress dashboard, open Plugins → Add New → Upload Plugin.
  3. Select the ZIP, install it, and activate AdPilot on the individual site.
  4. Open the AdPilot administration menu as an administrator.
  5. In Settings, review runtime diagnostics, transactional storage, and feature availability before configuring campaigns.

The archive contains a single adpilot/ directory. It includes the plugin bootstrap, runtime PHP, block metadata, shipped JavaScript/CSS, translations, and license/readme files. It excludes frontend source, build scripts, tests, specifications, development notes, dependencies, and local environment files.

Activation creates the implemented site-scoped schema, grants the plugin's capabilities to the WordPress administrator role, registers limited publisher roles, and schedules the plugin's maintenance event.

Install from a source checkout

The repository retains compiled files under assets/. For a source installation, place the project at wp-content/plugins/adpilot/, confirm those assets exist, and activate it through WordPress. Rebuild the assets whenever you change their TypeScript/CSS source. Use the packaged ZIP for a distribution containing only plugin runtime files.

Do not expect the checkout to create WordPress, a database, accounts, sample inventory, campaigns, or a public portal page automatically.

Quick start: first native campaign

This workflow demonstrates an explicitly authorized complimentary native campaign. It exercises the available delivery path without requiring checkout. Use a disposable site when evaluating the development preview.

1. Publish a placement

  1. Open AdPilot → Inventory and create a zone, such as Homepage sponsors.
  2. Add a container to that zone. For an initial test, use one logical slot and a simple grid.
  3. Open Layout Studio, create a new draft, and include the zone/container.
  4. Save changes, validate the saved draft, and publish the layout.
  5. Insert an Ad Zone or Ad Slot block into a page and select the published reference. A shortcode or PHP helper can also render the placement.

Publication makes inventory available for placement and booking. It does not create a campaign or guarantee that an ad will appear.

2. Create the advertiser workspace

  1. Create and publish a WordPress page containing the Shortcode block with [adpilot_portal].
  2. Sign in with the WordPress account that should own the advertiser organization.
  3. Create the organization from the portal's Organization screen. Organization creation requires either the publisher's organization-management capability or the Advertiser organization signup setting to be enabled.
  4. As a publisher with organization-management permission, open Campaigns → Advertiser organizations and verify the organization with a recorded reason.

For a one-account evaluation, an administrator can create an organization and becomes its owner. For another advertiser, enable organization signup, let that advertiser create their own organization while signed in, and then verify it as the publisher. Creating an organization from the publisher console assigns ownership to the account performing the action; it does not provision membership for an arbitrary other user.

Organization signup does not create WordPress accounts or enable WordPress's public user registration. Account provisioning remains a separate site responsibility. Secure invitations and ownership-transfer workflows are unfinished.

3. Submit and review a creative

  1. In the advertiser portal, select the organization and open Creatives.
  2. Create a text creative with an internal name, display title, body copy, and destination URL.
  3. Save the draft and submit it for review.
  4. As a publisher moderator, open Creatives, review that exact version, and approve it with a reason.

An approved organization does not imply an approved creative. Every creative version has its own content digest and review disposition.

4. Create and activate the campaign

  1. In the advertiser portal, create a campaign draft referencing the organization, published slot, exact creative version, and a start/end schedule.
  2. As the publisher, open Settings, record the applicable approved manual-activation policy reference, and enable Manual campaign activation.
  3. In Campaigns, choose Manual activation for the draft and record the allocation reason.
  4. Open the placement page during the campaign's schedule and scroll the placement near the viewport.

Activation requires a verified organization, an approved creative version, and available capacity for the entire exclusive interval. It creates a complimentary reservation; it does not record a payment. Creating a campaign draft alone does not reserve inventory or make the campaign eligible.

If the placement remains empty, follow the delivery troubleshooting checklist. Measurement can remain disabled throughout this workflow; advertising delivery and analytics consent are separate concerns.

Inventory and Layout Studio

Inventory concepts

Object Meaning
Zone A named grouping of advertising inventory, such as a header or sidebar region.
Container A compact grid definition belonging to a zone, with a slot count and responsive presentation settings.
Logical slot A stable sellable position materialized from a container and ordinal.
Layout draft A versioned document referencing existing zones and containers.
Published revision An immutable saved layout snapshot with a current publication pointer.
Render occurrence One appearance of a placement on a page; repeated occurrences have distinct DOM/render identities.

Public object IDs are 26-character uppercase alphanumeric identifiers. They are distinct from WordPress post IDs and internal database keys. Use the public IDs returned by inventory screens/API responses when configuring integrations.

Responsive configuration and bounds

The current container compiler accepts:

Field Current range or format
slot_count 1–5,000 logical slots per container under the current operational quota
columns 1–24
tablet_columns 1–12
mobile_columns 1–6
gap 0–64 pixels
aspect_ratio Two positive integers from 1 to 99, separated by /, such as 4/1

There is no 500-slot product cap. A 500-slot fixture is retained as an integration test. The current renderer retrieves at most 5,000 slot rows per render call, so large zones spanning multiple containers need consideration of that bound. Rendering containers individually preserves their own responsive grid configuration.

The Studio canvas is a synthetic, nonbillable schematic with bounded representative cells. It does not load one live creative per configured slot. Paginated slot controls expose persisted logical identities separately from the schematic.

Save, validate, and publish are separate operations. Updates use row_version to detect conflicts. If someone changes a record while you edit it, reload its current state before retrying. Published container geometry and materialized sequence length cannot be casually resized; complete contract-impact migration and rollback workflows remain future work.

Rendering ads in WordPress

All supported paths use the shared PHP renderer and current delivery eligibility checks. Placement configuration cannot grant creative approval, a paid entitlement, or a complimentary allocation.

Gutenberg and the Site Editor

Block title Registered name Current behavior
Ad Slot adpilot/ad-slot References a published logical slot.
Ad Zone adpilot/ad-zone References published zone/container inventory.
Ad Repeater adpilot/ad-repeater Registered dynamic placement; full query-loop interval behavior is incomplete.
Ad Showcase adpilot/ad-showcase Registered dynamic placement; full curated showcase behavior is incomplete.

The blocks store compact references and render on the server. Editor previews are nonbillable and do not load live advertising. The custom capability adpilot_insert_blocks protects placement validation/preview endpoints; WordPress editors/authors do not automatically receive it from this plugin.

An interval attribute on the repeater is not proof of implemented automatic insertion into a query loop, article list, or product grid. Those integrations still need their adapters.

Shortcodes

Replace the uppercase placeholders below with actual public IDs:

[adpilot_slot id="SLOT_PUBLIC_ID"]
[adpilot_zone id="ZONE_PUBLIC_ID"]
[adpilot_container id="CONTAINER_PUBLIC_ID"]

The shortcode handlers also accept the corresponding slot_id, zone_id, or container_id attribute. The id form takes precedence when both are supplied.

The following aliases currently render a zone through the shared renderer:

[adpilot_repeater id="ZONE_PUBLIC_ID"]
[adpilot_showcase id="ZONE_PUBLIC_ID"]

For the authenticated advertiser application:

[adpilot_portal]

Anonymous portal visitors receive a WordPress sign-in link. The portal is separate from public catalog API access and requires current organization permissions for advertiser operations. Use one portal application mount per page.

PHP helpers and theme action

Use helpers in a theme template after WordPress/plugins have loaded:

<?php
if (function_exists('adpilot_render_zone')) {
    echo adpilot_render_zone('ZONE_PUBLIC_ID');
}

Available signatures:

adpilot_render_slot(string $slot_id, array $options = []): string;
adpilot_render_zone(string $zone_id, array $options = []): string;
adpilot_render_container(string $container_id, array $options = []): string;

Helpers return escaped markup; the caller chooses whether to echo it. The implemented option is ['preview' => true], which returns a nonbillable preview label. Missing/unpublished inventory, an unready schema, feeds, or a rendering/storage failure can produce an empty string so that the rest of the page remains readable.

The plugin also registers an explicit WordPress action that prints a zone:

<?php
do_action('adpilot_render_zone', 'ZONE_PUBLIC_ID');

There is no automatic insertion into every theme, product grid, or content loop. Place integrations explicitly and retain normal wp_head()/wp_footer() behavior so WordPress can load registered assets.

Public runtime behavior

Server output contains lightweight placement placeholders. The browser runtime observes placements near the viewport and requests current decisions in batches of up to 20, with at most two decision requests in flight and a bounded queue. A configured slot or placeholder is not an impression.

Eligible native text is inserted with text nodes and sponsored link attributes. Each occurrence receives a distinct render identity, including when server HTML is reused from a page cache. A page can repeat a placement without reusing its occurrence ID.

New decisions and signed event/click claims recheck current campaign eligibility. Continuous removal of a creative already painted into a visitor's page is not a completed stop-propagation feature in this build. If a decision request fails, the advertising area can remain empty while the underlying page stays usable.

Organizations and permissions

AdPilot separates publisher capabilities from advertiser organization membership. Being a WordPress administrator grants publisher capabilities, but does not implicitly grant access to every organization's advertiser routes.

Publisher roles

Activation grants the plugin capability set to the administrator role and creates these limited WordPress roles:

Role slug Initial capabilities
adpilot_adops View/manage inventory, publish layouts, view/manage campaigns, and view diagnostics
adpilot_moderator Moderate creatives
adpilot_finance View orders and financial ledger data
adpilot_analyst View aggregate analytics
adpilot_support View diagnostics

Each custom role also receives WordPress read. Menu destinations and REST operations check the relevant capability. Inventory publication, organization status, moderation, rates, financial reads, settings, privacy configuration, emergency stop, job retry, and block insertion have distinct capability boundaries.

Some declared capabilities reserve future functionality. For example, the presence of an export or financial-adjustment capability does not mean a complete export/refund workflow exists. The authoritative current capability list and initial role grants are in Plugin.php.

Advertiser organization roles

Membership role Read organization data Write campaigns/creatives Purchase and view finance Manage members
owner Yes Yes Yes Yes
manager Yes Yes No No
analyst Yes No No No
billing Yes No Yes No

Authorization uses current membership and organization state. Revoked memberships and suspended/closed organizations cannot perform normal advertiser operations. Purchase additionally requires organization verification. An owner cannot remove the last active owner through the implemented member-change path.

Member role/revocation endpoints exist for current memberships. Secure invitation, arbitrary member provisioning, and full ownership-transfer workflows are not complete. Organization creation assigns an active owner membership to the current WordPress user.

Creatives and campaign eligibility

Native creative content

The implemented native format requires:

Field Validation
name Required internal name; up to 191 characters
title Required display title; up to 120 characters
body Required body copy; up to 600 characters
destination_url Valid HTTP(S) URL without embedded username/password; maximum 2,048 characters

Text is validated and stripped of HTML tags, and the native content JSON has a 4 KiB bound. There is no raster attachment pipeline. Advertiser HTML, script content, and attachment-based creatives are rejected.

A version starts as draft, can be submitted as submitted, and can receive approved, rejected, or changes_requested after a reasoned review. Only draft versions can be submitted. To revise rejected/changed/approved material, create a successor version through:

POST /adpilot/v1/organizations/{org_id}/creatives/{creative_id}/versions

Supply a complete new native payload. The successor gets its own public ID, family version number, digest, and draft status. It does not inherit approval, overwrite prior bytes/reviews, or automatically replace a campaign's selected creative. The complete UI editing and creative-selection workflow remains unfinished.

Eligibility is the intersection of separate states

A campaign can serve only when the relevant current conditions agree:

  1. Its slot and zone are published.
  2. Its advertiser organization is verified.
  3. Its exact selected creative version is approved and its content digest is valid.
  4. Its financial state is paid or explicitly complimentary.
  5. An active matching reservation covers that slot and interval.
  6. The campaign is active and both campaign/contract schedules include the current time.
  7. Delivery is not stopped by the applicable campaign or site control.

Campaign creation records an unfunded draft. Publisher manual activation creates a capacity-checked complimentary entitlement only when the policy is enabled. Paid allocation requires a reconciled paid order and compatible reservation in the same organization; payment does not approve a creative.

Schedules use half-open intervals: [start, end). The start is included and the end is excluded. API input timestamps require an explicit timezone and second precision, such as 2030-01-15T09:00:00Z or 2030-01-15T12:30:00+03:30; they are normalized to UTC. The UI converts browser-local schedule inputs before sending them.

Publisher pause, resume, and emergency-stop actions require a reason. Resuming still depends on current approval and entitlement. Full campaign cancellation, completion automation, remedy handling, and the planned state-machine surface are incomplete.

Pricing, reservations, and commerce

Fixed-window pricing

Publish rates from a published slot's Publish fixed rate action in Inventory. The implemented model prices the entire requested booking window at the fixed quoted amount; it does not automatically calculate per-day charges or meter clicks/impressions.

Money crosses the API as exact canonical integer strings in minor units. For example, "1500" with currency USD and precision 2 represents USD 15.00. Floating-point amounts, ambiguous leading-zero integers, and gateway values exceeding configured precision are rejected rather than silently rounded.

A rate records currency precision, quote lifetime, hold lifetime, and a policy reference. Quote/hold TTLs are currently bounded to 60–3,600 seconds; currency precision is bounded to 0–4. The only implemented tax case is explicitly_not_applicable with an actual policy reference. A general tax calculation adapter and jurisdiction-specific workflows are unfinished.

Booking sequence

Step What it establishes
Published rate An immutable fixed-price offer definition
Quote A server-calculated immutable snapshot of the requested slot, schedule, amount, currency, terms, and expiry
Hold A temporary exclusive capacity claim, subject to expiry and conflicts
Advertising order An unpaid commercial record tied to a hold and quote
Verified capture A trusted money fact recorded through the commerce boundary
Campaign allocation Separate mapping to current approved creative and compatible contracted inventory

The portal's View rates & reserve flow implements quote → hold → order. Holds require an Idempotency-Key of 16–128 characters. Reuse the same key when retrying the same hold request; a key cannot be reused for a different quote. The slot row serializes conflicting writers, and expiry is checked after acquiring the lock. Adjacent half-open intervals do not overlap.

An expired or released hold cannot be used as a current entitlement. The settlement application records a late verified capture as requiring remediation instead of reviving expired inventory. Its synthetic tests cover amount mismatch, duplicate replay, and late capture; they do not certify a real gateway.

WooCommerce status

The optional adapter uses supported WooCommerce order CRUD, declares HPOS compatibility, and listens for authoritative payment completion. Checkout readiness defaults to false, and ordinary settings cannot enable the commerce fields. The frontend continues to report checkout as unavailable in this preview.

Installing WooCommerce, creating an order, visiting a success URL, or passing a browser payment flag is insufficient to claim paid delivery. Real gateway/tax integration, reversals, distinct additional captures, refunds, legal invoices, and full reconciliation/remedy workflows need implementation and verification before commercial rollout.

Settings reference

Settings are site-specific, versioned, and exposed through AdPilot → Settings and the protected settings API. Changes require the current row_version.

Setting Default Meaning and guard
emergency_stop false Stops new eligible delivery decisions/claims when enabled. Changing it requires adpilot_emergency_stop in addition to settings permission.
manual_entitlements_enabled false Permits reasoned complimentary allocation only when an applicable policy reference exists.
manual_policy_ref Empty string Reference to the publisher's approved complimentary-allocation policy.
measurement_enabled false Allows measurement only with a policy reference, finite retention, registered trusted consent adapter, and a permitted request context.
measurement_policy_ref Empty string Reference to the applicable measurement/privacy policy.
event_retention_days null No configured event retention until explicitly set; accepted configured values are 1–3,650 days. Required before enabling measurement.
self_service_signup false Lets authenticated WordPress users create their own advertiser organization. It does not register WordPress user accounts.
commerce_enabled false Returned as a commerce placeholder; unavailable through ordinary settings updates.
commerce_policy_ref Empty string Returned as a commerce placeholder; requires a future dedicated certification workflow.

Privacy-related fields also require adpilot_manage_privacy. A configuration reference records which real policy applies; entering a string does not constitute independent policy approval or certification.

The settings screen includes runtime diagnostics and backend feature availability. A present menu or declared capability is not sufficient evidence that a feature is enabled.

Privacy and measurement

Ad serving can work while measurement collection is disabled. The current collector requires all of the following:

  • measurement_enabled is true.
  • A nonempty measurement policy reference and finite event retention are configured.
  • A trusted server integration is registered on adpilot_consent_verdict.
  • That integration returns ['analytics' => true] for the current request/site.
  • WordPress reports the environment as production, and the request is outside the preview/admin exclusions.

The consent filter receives the default verdict ['analytics' => false] and context containing site_id. A real consent-management integration must derive a purpose-specific verdict from authoritative state. The plugin does not bundle a consent-management platform, and a browser boolean is not an authorization to collect data.

The current measurement vocabulary distinguishes a server served fact from an untrusted rendered_claim and a recorded click. Signed short-lived decision references bind claims to current site/campaign/version/render provenance. Deduplication is enforced through an event-key index. None of these events creates a charge or establishes certified viewability, billable CPC/CPM, or attribution.

The hourly maintenance callback deletes at most 1,000 raw events older than the configured retention per run. A complete privacy exporter/eraser, legal-hold workflow, aggregate retention system, heatmap pipeline, and replay system are not implemented. Configure production collection only within the available policy and implementation boundaries.

REST API

The current namespace is adpilot/v1. With pretty REST URLs, the base path is:

https://example.test/wp-json/adpilot/v1/

WordPress query-style REST URLs are also supported by the frontend client, for example https://example.test/index.php?rest_route=/adpilot/v1/catalog/placements.

Request and response conventions

  • Publisher routes enforce the operation's WordPress capability. Advertiser routes additionally check current organization membership/object scope.
  • The bundled applications use same-origin WordPress cookie authentication with X-WP-Nonce. Keep REST responses and personalized portal/bootstrap content out of shared caches.
  • Mutation payloads use JSON; the registered handler bounds request bodies to 64 KiB. More specific domain limits can be smaller.
  • Paginated lists use page and per_page, with a default of 50 and maximum of 100 rows per page. Supported resources accept search; not every route supports every filter.
  • List envelopes contain items, total, page, and per_page.
  • Versioned edits require the current row_version where implemented. Hold requests also require Idempotency-Key.
  • Object references use public IDs; money uses integer strings; timestamp inputs include a timezone.
  • Successful registered responses receive Cache-Control: private, no-store. Errors use WordPress REST error envelopes with adpilot_ codes and HTTP status data.

Example public catalog request:

curl 'https://example.test/wp-json/adpilot/v1/catalog/placements?per_page=25&page=1'

Example native creative request body for an authorized organization route:

{
  "name": "Spring sponsor text",
  "title": "A message from our sponsor",
  "body": "A short, plain-text message for this placement.",
  "destination_url": "https://example.test/sponsor"
}

Route guide

All paths below are relative to the namespace base. Braced segments are placeholders.

Area Selected implemented routes
Identity GET me, GET my-organizations
Organizations POST organizations, GET organizations/{org_id}, GET organizations/{org_id}/members
Publisher organization actions GET admin/organizations, POST admin/organizations/{org_id}/actions/verify, POST admin/organizations/{org_id}/actions/suspend
Member actions POST organizations/{org_id}/members/{member_id}/actions/change-role, POST .../actions/revoke
Inventory GET/POST admin/zones, PATCH admin/zones/{zone_id}, GET/POST admin/containers, PATCH admin/containers/{container_id}, GET admin/slots
Catalog GET catalog/placements, GET catalog/formats, GET catalog/packages
Layouts GET admin/layouts, POST admin/layouts/drafts, GET/PATCH admin/layouts/drafts/{draft_id}, POST .../actions/validate, POST .../actions/publish
Native creatives GET/POST organizations/{org_id}/creatives, POST organizations/{org_id}/creatives/{creative_id}/versions, POST .../actions/submit-review
Moderation GET admin/creative-reviews, POST admin/creative-reviews/{review_id}/actions/approve, POST .../actions/reject, POST .../actions/request-changes
Campaigns GET admin/campaigns, GET/POST organizations/{org_id}/campaigns, POST admin/campaigns/{campaign_id}/actions/{schedule\|pause\|resume\|emergency-stop}
Rates/quotes GET/POST admin/rate-cards, POST organizations/{org_id}/quotes, GET organizations/{org_id}/quotes/{quote_id}
Holds POST organizations/{org_id}/holds, GET organizations/{org_id}/holds/{hold_id}, POST .../actions/release
Orders/ledger GET/POST organizations/{org_id}/orders, GET organizations/{org_id}/orders/{order_id}, POST .../actions/start-checkout (gated), GET admin/orders, GET admin/ledger
Delivery/measurement POST delivery/decisions, POST delivery/click, POST measurement/events, GET measurement/client-config
Reports GET admin/reports/overview, GET organizations/{org_id}/reports/campaigns
Configuration/operations GET/PATCH admin/settings, GET admin/diagnostics, GET admin/feature-availability, GET public/feature-capabilities, GET admin/audit-log, POST admin/jobs/{job_id}/actions/retry
Editor GET editor/zones, GET editor/zones/{zone_id}/preview, POST editor/placement-config/validate

This is a navigation guide, not a claim that every route in the specifications is implemented. Rest.php contains the authoritative current route registration and permission mapping. R2–R4 routes and outbound webhook endpoints are not registered.

Background processing and operations

Activation schedules the hourly adpilot_maintenance event, initially due approximately five minutes later. Its bounded callback dispatches committed local outbox events, applies configured raw-event retention, and processes a batch of expired active holds. Expired holds are excluded by authoritative capacity checks even before a maintenance pass updates their stored status.

The outbox uses schema-versioned site envelopes, processing leases, bounded retry attempts, and a local adpilot_domain_event action. Dispatch is at least once; extension consumers must deduplicate by event_id. It is not a complete external queue, notification service, or provider platform.

When WP-CLI loads the active plugin, these custom commands are available:

wp --path=/path/to/wordpress adpilot diagnostics
wp --path=/path/to/wordpress adpilot jobs run

adpilot diagnostics reports the plugin/schema versions, transactional readiness, and feature availability. adpilot jobs run drains the outbox; it does not execute the entire maintenance callback, purge all expired data, or certify delivery.

Monitor Settings diagnostics for pending background events and the next scheduled maintenance run. Ensure the site's normal WordPress cron execution is functioning. If a maintenance backlog exceeds a batch, processing requires subsequent runs. A complete scheduled-job dashboard, notification pipeline, and support-bundle export remain unfinished.

Public rendering catches ad-storage failures and returns an empty result. REST errors suppress raw SQL, server paths, secrets, and request contents. Site-level PHP/WordPress diagnostics are still needed to investigate an underlying failure.

Data lifecycle, upgrades, and removal

Storage and migration

The implemented schema has 23 plugin persistence tables under the site's WordPress table prefix, for example wp_adpilot_zones. It covers organizations/memberships, inventory/layouts, creative versions/reviews, campaigns, rates/quotes, holds/reservations, orders/ledger, delivery events, audit, outbox, and migration records.

Repositories bind themselves to the current WordPress site and include site predicates. Switching sites requires the corresponding site-bound service context. Site-specific configuration, capability/schema version markers, and cron state also live in WordPress options/roles.

Schema installation/migration runs on activation. Migration 001 has a recorded checksum, and activation refuses a mismatched recorded definition or an unsupported downgrade. Do not edit an installed migration definition or manually lower version options to force old code to run. New schema work needs an explicit reviewed migration and recovery plan.

Updates and backups

Back up the WordPress database, plugin files, site configuration, and any custom integration before testing an upgrade. Exercise changes on a disposable/staging installation first, including schema and role behavior. No production backup/restore certification or general one-click rollback is supplied by this preview.

Deactivation and uninstall

Deactivation clears the plugin's own scheduled maintenance hook and retains business data. The uninstall handler deliberately preserves inventory, organizations, contracts, ledger, audit records, options, and role/capability state. Deleting the plugin files is therefore not a data-erasure operation.

A complete destructive lifecycle/erasure workflow is not implemented. Any separately authorized removal procedure must account for financial retention and related WordPress metadata; the plugin does not provide a blanket purge command.

Development and packaging

Run commands from the repository root. The examples use ordinary command names; if your local environment requires a wrapper such as RTK, prefix them with that wrapper.

Toolchain

Tool Purpose
Node.js and npm Install pinned build dependencies and compile the TypeScript/React source. CI selects Node.js 24.
PHP CLI 8.2+ Run domain checks, syntax lint, and the packager. proc_open must be available for the lint subprocesses.
PHP mbstring Run the same text-validation paths used by the plugin.
PHP zip / ZipArchive Create the installable ZIP.
WordPress, InnoDB database, and WP-CLI Run the optional integration fixtures against a disposable local site.

There is no Composer dependency installation or Python toolchain. Development packages

This README is longer than the copy stored here. Read the rest on GitHub →