Quick Answer
Step-by-step process to add pages from Obsidian to WordPress, handle links, taxonomy, and validation.
Purpose: Prevent sync errors and link issues by following a repeatable process from drafting in Obsidian to publishing on WordPress.
Quick refs
- WordPress (Local):
/Users/cosmodrome/Local Sites/employment-law-aid - Scripts:
/Users/cosmodrome/Local Sites/employment-law-aid/scripts - Obsidian Vault Project:
/Users/cosmodrome/Library/Mobile Documents/iCloud~md~obsidian/Documents/Second Brain/8 - Projects/Employment Law Aid
0) Prerequisites
- WP-CLI installed and working for the local site.
- Frontmatter on all content follows project schema at minimum:
slug,title,description,status(Draft or Published), optional:type,funnel_stage,related_pages,target_keywords.
- Slug conventions
- National/topic hubs:
eeoc,eeoc/deadlines,eeoc/right-to-sue, etc. - Hierarchies:
eeoc/offices/california(never encode the full path intopost_name; use parent-child).
- National/topic hubs:
0.5) Keyword research workflow (inputs and steps)
Primary inputs
- CSV export:
/Users/cosmodrome/Downloads/organic-keywords-employmentlawhelp_org.csv - Internal note:
/Users/cosmodrome/Library/Mobile Documents/iCloud~md~obsidian/Documents/Second Brain/8 - Projects/Employment Law Aid/Competitor Keyword Opportunities - Priority List.md
Steps
- Review the Priority List note (Executive Summary + Priority Queue) to set near-term targets.
- Scan the CSV for high-volume, low-difficulty clusters not yet covered in the vault (difficulty ≤10 as quick wins).
- Map keywords → proposed slugs using existing URL conventions and hierarchy.
- Check for existing pages by slug to avoid duplicates.
- Add target_keywords to frontmatter when drafting new pages.
Optional helpers
- Quick check for proposed slug existence (replace list with candidates):
python3 - <<'PY'
import os, re, json
ROOT = "/Users/cosmodrome/Library/Mobile Documents/iCloud~md~obsidian/Documents/Second Brain/8 - Projects/Employment Law Aid"
FM = re.compile(r"^---\n(.*?)\n---", re.S); FLD = re.compile(r"^([A-Za-z0-9_\-]+):\s*(.*)$")
slugs = {}
for root,_,files in os.walk(ROOT):
for fn in files:
if not fn.endswith('.md'): continue
p=os.path.join(root,fn); t=open(p,'r',encoding='utf-8').read(); m=FM.match(t)
if not m: continue
fm={}
for line in m.group(1).splitlines():
m2=FLD.match(line.strip() or '')
if m2:
k,v=m2.group(1),m2.group(2).strip().strip('"').strip("'")
if k=='slug': slugs[v.strip('/ ')] = p
print(json.dumps({s: slugs.get(s) for s in [
'north-carolina/at-will-employment-getting-fired',
'colorado/at-will-employment-getting-fired',
'oregon/at-will-employment-getting-fired',
'new-york/wrongful-termination/warn-act',
'florida/wages-and-hours/meal-break-laws',
'michigan/wages-and-hours/meal-break-laws',
'pennsylvania/wages-and-hours/meal-break-laws',
'discrimination/workplace-discrimination-examples',
'resources/sample-employment-complaint-letters'
]}, indent=2))
PY
1) Content prep in Obsidian
- Linking policy in the vault:
- Use standard markdown links (not wiki-links) while drafting.
- Prefer relative
.mdlinks between notes during editing for readability. - Before syncing to WordPress, convert any
.mdlinks to site URLs based on the target note’sslug(see Link conversion step).
- Frontmatter
- Ensure every note has a
slugand correctstatus.
- Ensure every note has a
2) Build/refresh slug index
Regenerate the slug map used by link tools and QA.
python3 tools/build_slug_index.py
3) Create missing WordPress pages (hierarchical)
Creates pages according to slug, building parents first and adding taxonomy.
python3 "/Users/cosmodrome/Local Sites/employment-law-aid/scripts/create_missing_pages.py"
Notes
- Existence is checked by
(post_parent, post_name)pairs. - Content-level taxonomy is auto-assigned by depth:
- Depth 0–1:
content-level = topic - Depth ≥2:
content-level = l3-spoke
- Depth 0–1:
4) Link conversion (relative .md → site URLs)
Convert remaining .md links to their final site URLs using the slug index.
Run the link rewriter:
python3 "/Users/cosmodrome/Local Sites/employment-law-aid/scripts/rewrite_internal_links.py" \
--md-dir "/Users/cosmodrome/Library/Mobile Documents/iCloud~md~obsidian/Documents/Second Brain/8 - Projects/Employment Law Aid" \
--only-prefix eeoc
Options
- Add
--include-htmlto convert HTML anchors as well. - Omit
--only-prefixto process the entire vault. - Use
--dry-runto preview changes.
After conversion, all internal links in markdown should look like /eeoc/deadlines/ (absolute site paths), not ./EEOC - Deadlines (...).md.
5) Sync content to WordPress (safe)
Uploads post_content + ACF H1 + Rank Math meta for notes with status: Draft.
python3 "/Users/cosmodrome/Local Sites/employment-law-aid/scripts/sync_published_safe.py"
Notes
- Script finds the WP page by hierarchical slug matching.
- On successful upload, it updates the note’s status to
Published.
6) Validate internal links against the site
Crawl internal links and ensure no 404s.
python3 "/Users/cosmodrome/Local Sites/employment-law-aid/scripts/validate_links.py" \
--md-dir "/Users/cosmodrome/Library/Mobile Documents/iCloud~md~obsidian/Documents/Second Brain/8 - Projects/Employment Law Aid" \
--base-url "http://employment-law-aid.local"
If broken links are reported
- Fix the source markdown (usually lingering
.mdlinks or bad filename mappings). - Re-run the sync script to push corrected content.
7) Duplicate/flattened page cleanup (as needed)
If a script created flat pages like eeoc-offices-new-york at the top level, remove them.
List candidates (example for eeoc- prefix):
wp --path="/Users/cosmodrome/Local Sites/employment-law-aid/app/public" --no-color \
post list --post_type=page --fields=ID,post_name,post_parent --format=json \
| jq '[.[] | select((.post_parent==0) and (.post_name|startswith("eeoc-")) and (.post_name!="eeoc")) ]'
Delete by ID (example):
wp --path="/Users/cosmodrome/Local Sites/employment-law-aid/app/public" --no-color post delete <ID> --force
8) Final checks
- Spot-check a few published pages in the browser for layout and content.
- Verify taxonomy levels (topic vs. l3-spoke) appear correctly in any filters/menus.
Troubleshooting
- Page exists but content is blank:
- Ensure the content section after the first
# H1is non-empty. - Re-run the sync script; check for WP-CLI errors.
- Ensure the content section after the first
- “No matching WP page” during sync:
- Run the page creation script (Step 3) and try again.
- Obsidian links published as
.mdfiles:- Run the Link conversion step (Step 4), then re-sync.
Change log
- 2026-11-10: Added hierarchical page creation and taxonomy assignment to
create_missing_pages.py; documented link conversion + validation workflow.
