Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Extract, suggest, and sync tags and categories for blog posts across all major CMS platforms. Supports WordPress REST API, Shopify GraphQL, Ghost Content API, Strapi REST/GraphQL, and Sanity GROQ. Generates tag suggestions from content analysis (keyword frequency, heading extraction, semantic grouping), enforces minimum post-count thresholds to prevent thin tag archives, and syncs taxonomy via authenticated API calls. Use when user says "tags", "categories", "taxonomy", "tag suggestions", "sync
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 352% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 76% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 78% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 52% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 78% | 0% |
Manage tags, categories, and topic clusters across CMS platforms.
| Command | Purpose | |---------|---------| | /blog taxonomy suggest <file> | Extract candidate tags and categories from content | | /blog taxonomy sync <cms> | Push taxonomy to CMS via authenticated API | | /blog taxonomy audit [directory] | Check for thin tags, orphan tags, taxonomy bloat |
Read the target file and extract:
Scan the body text for high-frequency phrases:
Exclude common non-tag words: articles, prepositions, conjunctions, pronouns.
Group related candidates into clusters:
(frequency * 2) + (heading_presence * 5) + (emphasis * 1)## Tag Suggestions: [Post Title]
| Rank | Tag | Score | Source |
|------|-----|-------|--------|
| 1 | content-marketing | 18 | H2 + 6 mentions |
| 2 | seo-strategy | 14 | H3 + 4 mentions |
| 3 | keyword-research | 11 | 5 mentions + bold |
### Suggested Categories
- Primary: [best-fit category]
- Secondary: [optional second category]| CMS | API Type | Auth Method | Tags Model | |-----|----------|-------------|------------| | WordPress | REST | Application Passwords (base64) | First-class entities with IDs | | Shopify | GraphQL (Admin API) | Admin API access token | String array on Article | | Ghost | REST (Admin API) | API key with JWT signing | First-class entities | | Strapi | REST or GraphQL | API token (Bearer) | User-defined content type | | Sanity | GROQ / Mutations | Project token (Bearer) | Document type |
List tags:
GET {CMS_URL}/wp-json/wp/v2/tags?per_page=100&search={keyword}
Authorization: Basic {base64(username:app_password)}Create tag:
POST {CMS_URL}/wp-json/wp/v2/tags
Body: {"name": "Tag Name", "slug": "tag-name", "description": "Optional"}List categories (hierarchical, supports parent field):
GET {CMS_URL}/wp-json/wp/v2/categories?per_page=100Create category:
POST {CMS_URL}/wp-json/wp/v2/categories
Body: {"name": "Category", "slug": "category", "parent": 0}Assign tags to post:
POST {CMS_URL}/wp-json/wp/v2/posts/{id}
Body: {"tags": [1, 2, 3], "categories": [4]}Pagination: follow X-WP-TotalPages header for full listing.
Tags on Shopify are string arrays on the Article object, not first-class entities.
Update article tags (GraphQL Admin API):
graphqlmutation { articleUpdate(id: "gid://shopify/Article/123", article: { tags: ["tag-one", "tag-two", "tag-three"] }) { article { id tags } userErrors { field message } } }
List all tags in use (GraphQL):
graphql{ articles(first: 250, after: $cursor) { pageInfo { hasNextPage endCursor } edges { node { id title tags } } } }
Auth header: X-Shopify-Access-Token: {token}
Pagination: loop while pageInfo.hasNextPage is true, passing endCursor as the next $cursor.
Note: REST API marked legacy Oct 2024. GraphQL required for new apps since Apr 2025.
List tags:
GET {CMS_URL}/ghost/api/admin/tags/?limit=all
Authorization: Ghost {jwt_token}Create tag:
POST {CMS_URL}/ghost/api/admin/tags/
Body: {"tags": [{"name": "Tag Name", "slug": "tag-name"}]}JWT generation: sign with admin API key (id:secret format), iat = now, exp = 5 min, audience = /admin/.
Endpoint auto-generated from content types. Typical setup:
GET {CMS_URL}/api/tags?pagination[pageSize]=100
POST {CMS_URL}/api/tags
Body: {"data": {"name": "Tag Name", "slug": "tag-name"}}
Authorization: Bearer {api_token}Pagination: increment pagination[page] until all pages are exhausted.
Strapi v4 responses use the data wrapper with attributes; Strapi v5 uses a flatter response shape. Detect the version or normalize both shapes before deduplication. Check your content type schema for field names.
Query tags (GROQ):
*[_type == "tag"] { _id, name, slug }Create tag (Mutations API):
POST https://{project_id}.api.sanity.io/{SANITY_API_VERSION}/data/mutate/{dataset}
Body: {"mutations": [{"create": {"_type": "tag", "name": "Tag", "slug": {"current": "tag"}}}]}
Authorization: Bearer {token}Default SANITY_API_VERSION to a current tested API date supplied by the project environment; do not hard-code it in generated requests.
Scan all posts in the target directory (or fetch from CMS). Build a map:
| Check | Threshold | Action | |-------|-----------|--------| | Thin tag archives | < 5 posts per tag | Review for merge or noindex after traffic, intent, and link checks | | Orphan tags | 0 posts | Recommend deletion | | Tag bloat | More than max(50, post_count * 0.25) total tags, adjusted for taxonomy purpose | Recommend consolidation | | Category depth | > 3 levels | Recommend flattening | | Uncategorized posts | No category assigned | Assign to appropriate category | | Duplicate slugs | Same slug, different name | Merge into canonical version |
Group findings by priority:
## Taxonomy Audit: [Site/Directory]
**Total tags**: [n] | **Total categories**: [n]
**Healthy**: [n] | **Thin**: [n] | **Orphan**: [n]
### Critical Issues
- [orphan tags list]
### Recommendations
1. Merge [tag-a] and [tag-b] (same topic, [n] combined posts)
2. Delete orphan tags: [list]
3. Merge or noindex tag archives with < 5 posts only after traffic, intent, and link checks| Variable | Purpose | Example | |----------|---------|---------| | CMS_TYPE | Platform identifier | wordpress, shopify, ghost, strapi, sanity | | CMS_URL | HTTPS base URL of the CMS | https://example.com | | CMS_ALLOWED_HOSTS | Optional comma-separated allowlist for CMS hosts | example.com,admin.example.com | | CMS_USERNAME | WordPress username when using Application Passwords | editor@example.com | | CMS_API_KEY | Authentication credential | WordPress app password, API token, or key | | SANITY_API_VERSION | Sanity API date for mutations | v2026-07-01 |
These must be set in the shell environment. Never store credentials in files or commit them to version control. The skill reads them via $CMS_TYPE, $CMS_URL, $CMS_USERNAME, $CMS_API_KEY, and optional platform-specific variables at runtime.
Security rule for CMS calls: require HTTPS, allow only http and https parsing paths but send authenticated requests over HTTPS only, resolve DNS and block loopback/private/link-local/reserved IPs, validate redirects with the same checks or disable redirects, cap timeouts at 10 seconds, and enforce CMS_ALLOWED_HOSTS when set.
Retry-After when present; otherwise use exponential backoff and retry once. Report if the limit persistsOther measured skills in the registry, with their headline benchmark lift.