BDN Metadata
WordPress plugin: auto-populates post metadata from Airtable on import
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.zipWordPress 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:
- Looks up the story in Airtable by its Google Drive file ID
- Sets the WordPress category based on the Airtable desk/section field, using Claude Haiku to pick the best match when multiple candidates exist
- Adds an impact tag from the Airtable Impact field
- Sets the post author by matching the Airtable Reporter field to a WordPress user display name
- Schedules the publish date and time from Airtable (Eastern time, future dates only)
- Pre-populates the Nota SEO title with the story headline
- Prepends an empty
bdn/story-summaryblock 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_insertfilter) - 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
- Upload the
bdn-metadatafolder towp-content/plugins/ - Activate the plugin in Plugins → Installed Plugins
- 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:
- Open the story template Google Doc
- Extensions → Apps Script
- Replace the default code with the contents of
Code.gs - Set the script properties (
AIRTABLE_TOKEN,AIRTABLE_BASE_ID,AIRTABLE_TABLE_NAME) via Project Settings → Script Properties - Save and authorize
How category resolution works
- The plugin looks up the story's Section field in the Desk → Category Map
- If there's exactly one candidate slug, it's used directly — no API call
- If there are multiple candidates, Claude Haiku receives the desk name and headline and returns a single slug
- 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
Related plugins
- bdn-summary-block — registers the
bdn/story-summaryGutenberg block that this plugin inserts. Both must be active for the AI summary workflow to function.