Semantic Decay & Freshness Engine for Strapi v5
Detect stale dates, outdated version references, and rotting content β before your readers do.
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.
| 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 |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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 β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# From npm (once published)
npm install strapi-plugin-content-pulse
# From source
cd your-strapi-project
npm install ../path/to/contentpulse-strapi// 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,
},
},
}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."
}
]
}
}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 morningsA 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)
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"
}
]
}Trigger a fresh analysis on a single document.
curl -X POST \
/api/content-pulse/reanalyze/api::article.article/abc123xyz789Response:
{
"data": {
"score": 75,
"warnings": [...],
"lastAnalyzedAt": "2026-05-28T10:15:00.000Z"
}
}| 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)
// 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'
// }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
- Broken link detection (
url_rotwarning type) - Custom decay rules (user-defined regex patterns)
- Locale-aware analysis for multilingual setups
- npm package publish
- CSV/JSON export for auditing
MIT Β© Mehmet Turac