Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ«€ ContentPulse β€” Strapi v5

Semantic Decay & Freshness Engine for Strapi v5

Detect stale dates, outdated version references, and rotting content β€” before your readers do.

Strapi v5 TypeScript License: MIT GitHub


The Problem

Your CMS has a freshness problem you don't know about yet.

A blog post from 2022 still says "Node.js v14 is recommended." A tutorial references an API deprecated last year. Docs say "latest: 3.2.1" β€” it's now 4.1.0. Nobody catches it. Your readers do.

ContentPulse fixes this.


What It Does

Feature Description
πŸ—“ Date Decay Flags stale dates using chrono-node NLP parsing
πŸ”– Version Decay Detects outdated version strings (v3.x, 2022 edition)
πŸ“Š Freshness Score 0–100 per document, severity-weighted
⚑ Lifecycle Hooks Auto-analyze on every create/update
πŸ”” Webhook Alerts Slack / Teams / custom HTTP when score drops below threshold
πŸ• Cron Bulk Scan Daily re-analysis of all monitored content β€” no editor action needed
πŸ–₯ Freshness Dashboard Admin UI: all collections sorted by score, filter by severity, re-analyze on demand
πŸ”Œ REST API /api/content-pulse/dashboard + /reanalyze/:uid/:id

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Strapi v5 Instance                        β”‚
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   save    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Content Type β”‚ ────────► β”‚  Lifecycle Hook         β”‚   β”‚
β”‚  β”‚  (article,    β”‚           β”‚  afterCreate / afterUpdateβ”‚  β”‚
β”‚  β”‚   post, docs) β”‚           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚                 β”‚
β”‚                                           β–Ό                 β”‚
β”‚                               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚                               β”‚  analyzeContent()   β”‚      β”‚
β”‚                               β”‚  β”œβ”€ Date Decay      β”‚      β”‚
β”‚                               β”‚  └─ Version Decay   β”‚      β”‚
β”‚                               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                                            β”‚                β”‚
β”‚               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                β”‚
β”‚               β”‚            β”‚               β”‚                β”‚
β”‚               β–Ό            β–Ό               β–Ό                β”‚
β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚      β”‚ DB: _pulse   β”‚ β”‚Webhook  β”‚ β”‚ Admin Panel  β”‚        β”‚
β”‚      β”‚ Score/Warn   β”‚ β”‚Notifier β”‚ β”‚ Dashboard    β”‚        β”‚
β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Cron Scheduler (daily 03:00)                        β”‚  β”‚
β”‚  β”‚  β†’ Bulk scans ALL monitored docs in batches of 50    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Installation

# From npm (once published)
npm install strapi-plugin-content-pulse

# From source
cd your-strapi-project
npm install ../path/to/contentpulse-strapi

Configuration

// config/plugins.ts
export default {
  'content-pulse': {
    enabled: true,
    config: {
      // Collections to monitor (required)
      collections: [
        'api::article.article',
        'api::post.post',
        'api::doc.doc',
      ],

      // Score floor β€” warn below this (default: 80)
      warningThreshold: 80,

      // Days before a date reference is considered stale (default: 365)
      maxAgeDays: 365,

      // Webhook URL for Slack / Teams / custom HTTP (optional)
      webhookUrl: process.env.CONTENT_PULSE_WEBHOOK_URL,

      // Optional Slack channel override (e.g. '#content-alerts')
      slackChannel: process.env.CONTENT_PULSE_SLACK_CHANNEL,

      // Cron expression for bulk re-analysis (default: daily at 03:00)
      cron: '0 3 * * *',

      // Documents per batch during bulk scan (default: 50)
      batchSize: 50,
    },
  },
}

Webhook Notifications

When a document's freshness score drops below warningThreshold, ContentPulse fires a webhook.

Slack β€” auto-detected by URL, sends Block Kit cards:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ”΄ ContentPulse Alert                  β”‚
β”‚                                         β”‚
β”‚  Content Type    Freshness Score        β”‚
β”‚  article         32/100                 β”‚
β”‚                                         β”‚
β”‚  Decay Warnings                         β”‚
β”‚  β€’ [CRITICAL] Stale date: "Jan 2022"    β”‚
β”‚  β€’ [HIGH] Outdated: "v3.2.1"            β”‚
β”‚                                         β”‚
β”‚  [Review in Strapi β†’]                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Generic HTTP β€” sends structured JSON:

{
  "event": "content_pulse.decay",
  "timestamp": "2026-05-28T03:00:00.000Z",
  "data": {
    "documentId": "abc123",
    "contentType": "api::article.article",
    "score": 32,
    "warnings": [
      {
        "type": "date_decay",
        "severity": "critical",
        "message": "Stale date: \"January 2022\" (1600 days ago)",
        "originalText": "January 2022",
        "suggestion": "Consider updating this reference."
      }
    ]
  }
}

Bulk Cron Re-Analysis

Content written years ago won't re-analyze itself unless an editor saves it again. ContentPulse solves this with an optional daily cron scan.

[03:00 daily]
  β†’ Scan api::article.article (batch 50)
  β†’ Scan api::post.post (batch 50)
  β†’ Update _pulseScore, _pulseWarnings, _lastAnalyzedAt
  β†’ Log: "420 docs scanned, 37 flagged as stale"

Override the schedule:

cron: '0 */6 * * *'  // every 6 hours
cron: '0 9 * * 1'    // Monday mornings

Freshness Dashboard

A full admin panel page listing all monitored documents by freshness score.

Features:

  • Stats bar (total, avg score, fresh / stale / critical counts)
  • Filter by severity (all / fresh / stale / critical)
  • Sort by score ascending or descending
  • Mini score bar per document
  • Worst severity badge with warning count
  • Per-row Re-analyze button (no page reload)

REST API

GET /api/content-pulse/dashboard

Returns freshness data for all monitored documents.

{
  "data": [
    {
      "id": 1,
      "documentId": "abc123xyz789",
      "contentType": "api::article.article",
      "title": "Getting Started with Node.js",
      "score": 32,
      "warnings": [...],
      "lastAnalyzedAt": "2026-05-28T03:00:00.000Z"
    }
  ]
}

POST /api/content-pulse/reanalyze/:uid/:documentId

Trigger a fresh analysis on a single document.

curl -X POST \
  /api/content-pulse/reanalyze/api::article.article/abc123xyz789

Response:

{
  "data": {
    "score": 75,
    "warnings": [...],
    "lastAnalyzedAt": "2026-05-28T10:15:00.000Z"
  }
}

Scoring System

Severity Deduction Trigger
Critical βˆ’25 pts Date or version > 3 years old
High βˆ’15 pts Date or version > 2 years old
Medium βˆ’10 pts Date > 1.5 yrs old / version > 1 yr old
Low βˆ’5 pts Any detected stale pattern

Score range: 0 (fully decayed) β†’ 100 (completely fresh)


Manual Analysis

// In any Strapi service or controller
const pulseService = strapi.plugin('content-pulse').service('contentPulse')

const result = pulseService.analyzeContent(document.body, 365)

console.log(result)
// {
//   score: 55,
//   warnings: [
//     {
//       type: 'date_decay',
//       severity: 'high',
//       message: 'Stale date: "March 2024" (450 days ago)',
//       originalText: 'March 2024',
//       suggestion: 'Consider updating this reference.'
//     }
//   ],
//   analyzedAt: '2026-05-28T10:00:00.000Z'
// }

Project Structure

src/
β”œβ”€β”€ index.ts                          # Plugin entry, lifecycle hooks, REST routes
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ content-pulse.ts              # Core analyzer (chrono-node + regex)
β”‚   β”œβ”€β”€ notifier.ts                   # Webhook / Slack notifications
β”‚   └── scheduler.ts                  # Bulk cron re-analysis
└── admin/src/
    β”œβ”€β”€ components/
    β”‚   └── PulseWidget.tsx           # Per-document sidebar widget
    └── pages/
        └── FreshnessDashboard/
            └── index.tsx             # Full admin dashboard page

Roadmap

  • Broken link detection (url_rot warning type)
  • Custom decay rules (user-defined regex patterns)
  • Locale-aware analysis for multilingual setups
  • npm package publish
  • CSV/JSON export for auditing

License

MIT Β© Mehmet Turac

About

ContentPulse - Semantic Decay & Freshness Engine for Strapi v5. Detect stale dates and outdated version references before your readers do.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages