WP Manifestindependent plugin directory
manifest / content / bdn-metadata

BDN Metadata

WordPress plugin: auto-populates post metadata from Airtable on import

by Bangor Daily News · github.com/dsmacleod/bdn-metadata

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/dsmacleod/bdn-metadata/archive/refs/heads/main.zip

WordPress plugin that automatically populates post metadata when a story is imported from Google Docs via the Docs-to-WordPress plugin. Pulls budget data from Airtable, resolves the correct category using Claude AI, sets the author, schedules the publish date, and pre-populates Nota SEO fields — all without editor intervention.

What it does

When a story is imported from Google Docs, the plugin:

  1. Looks up the story in Airtable by its Google Drive file ID
  2. Sets the WordPress category based on the Airtable desk/section field, using Claude Haiku to pick the best match when multiple candidates exist
  3. Adds an impact tag from the Airtable Impact field
  4. Sets the post author by matching the Airtable Reporter field to a WordPress user display name
  5. Schedules the publish date and time from Airtable (Eastern time, future dates only)
  6. Pre-populates the Nota SEO title with the story headline
  7. Prepends an empty bdn/story-summary block to the post content so the AI summary block is ready to generate (requires the bdn-summary-block plugin)

If Airtable lookup fails or a field is missing, the import proceeds normally — nothing is blocked.

Requirements

  • WordPress 6.4+
  • PHP 8.1+
  • Docs-to-WordPress plugin (provides the pre_docs_to_wp_insert filter)
  • Nota plugin (already installed; this plugin writes to its SEO meta keys)
  • Airtable base with story budget data
  • Anthropic API key (Claude Haiku, used only for category resolution)

Installation

  1. Upload the bdn-metadata folder to wp-content/plugins/
  2. Activate the plugin in Plugins → Installed Plugins
  3. Go to Settings → BDN Metadata and fill in the configuration fields

No Composer dependencies are required at runtime. The vendor/ directory is only needed to run tests locally.

Configuration

All settings are at Settings → BDN Metadata.

API credentials

Field Description
Airtable Token Personal access token from airtable.com/create/tokens. Needs data.records:read scope on the budget base.
Airtable Base ID Found in the Airtable API docs for your base (appXXXXXXXX).
Airtable Table Name Exact name of the table containing story budget records.
Claude API Key Anthropic API key. Used only to resolve ambiguous category assignments.

Airtable field names

These tell the plugin which column in Airtable corresponds to each piece of metadata. Enter the exact field names as they appear in Airtable.

Setting Airtable column Example
Google Doc ID Field Name The field that stores the Google Drive file ID (written by the companion Google Apps Script) Google Doc ID
Section Field Name Editorial desk or section Section
Impact Field Name Story impact tag Impact
Reporter Field Name Reporter display name Reporter
Publish Date Field Name Scheduled publish date Publish Date
Publish Time Field Name Scheduled publish time Publish Time

Desk → Category Map

A JSON object mapping Airtable Section values to WordPress category slugs. The first slug in each array is the default fallback if Claude can't confidently pick one.

{
  "City": ["bangor", "penobscot", "business"],
  "Sports": ["sports"],
  "State": ["maine", "politics", "education"],
  "Nation/World": ["nation", "world"]
}

Section values must match exactly what Airtable returns. Category slugs must exist in WordPress.

Google Apps Script

A companion Apps Script writes the Google Drive file ID back to the Airtable budget record when a Google Doc is created from the story template. This is what connects a Google Doc to its Airtable row.

The script lives at google-apps-script/Code.gs in this repo. To deploy:

  1. Open the story template Google Doc
  2. Extensions → Apps Script
  3. Replace the default code with the contents of Code.gs
  4. Set the script properties (AIRTABLE_TOKEN, AIRTABLE_BASE_ID, AIRTABLE_TABLE_NAME) via Project Settings → Script Properties
  5. Save and authorize

How category resolution works

  1. The plugin looks up the story's Section field in the Desk → Category Map
  2. If there's exactly one candidate slug, it's used directly — no API call
  3. If there are multiple candidates, Claude Haiku receives the desk name and headline and returns a single slug
  4. If the API call fails or returns an unrecognized slug, the first slug in the array is used as fallback

The fallback ensures a category is always set even if the API is unavailable.

What gets stored

WordPress field Source
post_category Resolved from Airtable Section + desk map
tags_input Airtable Impact field
post_author Matched to WP user by display name
post_date / post_date_gmt Airtable Publish Date + Time (America/New_York)
post_status Set to future if publish date is in the future
meta_input[nota_seo_page_title] Post title (headline)
meta_input[_airtable_record_id] Airtable record ID (for future sync)
meta_input[_unknown_reporter] Reporter name when no matching WP user is found
post_content (prepended) Empty bdn/story-summary block

Running tests

Tests require a local WordPress test environment. Set up with wp-phpunit/wp-phpunit:

composer install
WP_TESTS_DIR=vendor/wp-phpunit/wp-phpunit vendor/bin/phpunit

For a full integration test with live Airtable and Claude credentials, copy tests/integration-config.example.php to tests/integration-config.php and fill in credentials before running.

File structure

bdn-metadata/
├── bdn-metadata.php              ← Plugin entry point, autoloader, hook registration
├── includes/
│   ├── class-admin-settings.php  ← Settings page and option accessors
│   ├── class-airtable-client.php ← Fetches budget records from Airtable API
│   ├── class-category-resolver.php ← Resolves desk → category slug via Claude Haiku
│   ├── class-import-hook.php     ← Hooks into pre_docs_to_wp_insert, orchestrates enrichment
│   └── class-nota-bridge.php     ← SEO meta key constants, description truncation utility
├── tests/
│   ├── bootstrap.php
│   ├── test-admin-settings.php
│   ├── test-airtable-client.php
│   ├── test-category-resolver.php
│   ├── test-import-hook.php
│   └── test-nota-bridge.php
├── composer.json
└── phpunit.xml.dist
  • bdn-summary-block — registers the bdn/story-summary Gutenberg block that this plugin inserts. Both must be active for the AI summary workflow to function.