# Bidirectional Links Configuration

**Last Updated:** 2026-02-25

Config-driven bidirectional in-content links for blog posts. Replaces per-post scripts (add-recruiting-links.php, add-cost-per-hire-links.php, add-mitarbeitergespraech-links.php, etc.) for posts that use the standard search/replace pattern.

## Config Location

```
docs/content/blog/posts/{category}/{slug}/data/bidirectional-links.json
```

Examples:

- `docs/content/blog/posts/lexikon/recruiting/data/bidirectional-links.json`
- `docs/content/blog/posts/lexikon/cost-per-hire/data/bidirectional-links.json` (adds Cost per Hire links into recruiting)

## Schema

```json
{
  "version": "1.0",
  "source_post": "recruiting",
  "source_category": "lexikon",
  "link_url": "/insights/lexikon/recruiting/",
  "link_anchor": "Recruiting",
  "link_variants": {
    "plural": "Mitarbeitergespräche",
    "dativ": "Mitarbeitergespräch"
  },
  "content_targets": [
    {
      "post": "personalmarketing",
      "category": "lexikon",
      "replacements": [
        ["search text without link", "search text with {{LINK}}"]
      ]
    }
  ],
  "faq_targets": [
    {
      "post": "bewerbermanagementsystem",
      "category": "lexikon",
      "replacements": [
        ["search text without link", "search text with {{LINK}}"]
      ]
    }
  ]
}
```

### Required Fields

| Field | Type | Description |
|-------|------|-------------|
| `link_url` | string | URL path (e.g. `/insights/lexikon/recruiting/`) |
| `link_anchor` | string | Default anchor text |
| `content_targets` | array | Array of `{ post, category, replacements }` |
| `faq_targets` | array | Array of `{ post, category, replacements }` |

### Optional Fields

| Field | Type | Description |
|-------|------|-------------|
| `link_variants` | object | `plural`, `dativ`, etc. for inflection variants |
| `version` | string | Config version (for future schema changes) |
| `source_post` | string | Source post slug (for documentation) |

### Placeholders in Replacements

| Placeholder | Resolves to |
|------------|-------------|
| `{{LINK}}` | `<a href="{link_url}">{link_anchor}</a>` |
| `{{LINK_PLURAL}}` | `<a href="{link_url}">{link_variants.plural}</a>` (if defined) |
| `{{LINK_DATIV}}` | `<a href="{link_url}">{link_variants.dativ}</a>` (if defined) |

## Usage

```bash
# Dry-run (preview changes)
php v2/scripts/blog/add-bidirectional-links.php --post=recruiting --category=lexikon --dry-run

# Apply changes
php v2/scripts/blog/add-bidirectional-links.php --post=recruiting --category=lexikon

# Use custom config path
php v2/scripts/blog/add-bidirectional-links.php --post=recruiting --category=lexikon --config=/path/to/bidirectional-links.json
```

## Behavior

- **Content updates:** Uses `update-post-content.php --backup` for body content
- **FAQ updates:** Direct JSON edit (no backup; run `backup-blog-content.py --manual` per blog-backup.mdc)
- **Skip logic:** Skips targets where the link URL is already present in content or FAQs
- **Idempotent:** Safe to run multiple times; already-linked targets are skipped

## When to Use

- **Standard pattern:** Use config + generic script when you have simple search/replace pairs (e.g. "Recruiting" → "Recruiting" with link).
- **Custom logic:** Keep a dedicated script (e.g. `add-assessment-center-links.php`) when you need:
  - Link swaps (change existing link to target different post)
  - Regex replacements
  - Special handling

## New Post Workflow

1. Create `docs/content/blog/posts/{category}/{slug}/data/bidirectional-links.json` with link definitions
2. Run `php v2/scripts/blog/add-bidirectional-links.php --post=slug --category=lexikon --dry-run`
3. Run without `--dry-run` to apply

## Related

- Script: `v2/scripts/blog/add-bidirectional-links.php`
- Rule: `.cursor/rules/blog-new-post-creation.mdc`
- Backup: `docs/content/blog/BACKUP_GUIDE.md`
