Skip to content

Conversation

@mmenanno
Copy link

Description

This PR refactors the webhook notification agent to use Handlebars templating instead of the custom KeyMap system, providing a foundation for standardized templating across all notification agents.


Key Changes

  • Replaced KeyMap with Handlebars — Removed 120+ lines of custom template logic and replaced with standard Handlebars engine
  • Created TemplateEngine class (server/lib/notifications/templateEngine.ts) — Centralized template rendering with custom helpers
  • Custom Handlebars helpers — Added comparison (eq, ne, lt, gt, lte, gte), logical (and, or), and string formatting helpers (capitalize, upper, lower, truncate, json, default)
  • JSON cleanup logic — Automatically removes trailing commas from Handlebars conditional rendering (ran into this issue when I was testing)
  • User fallback — Auto-approved/auto-requested notifications correctly show the requesting user's information (another thing I ran into while testing)
  • Frontend validation — Updated to allow Handlebars syntax ({{#if}}, {{#each}}, etc.) while still validating plain JSON
  • No HTML escaping — Proper character rendering (e.g., apostrophes display correctly which was an issue I found while testing)

New Templating Capabilities

Users can now use advanced template features in webhook payloads:

  • Conditionals: {{#if media}}...{{/if}}, {{#if (eq status "PENDING")}}...{{/if}}
  • Comparisons: {{#if (gt request_count 10)}}...{{/if}}
  • Formatting: {{capitalize user_name}}, {{upper media_type}}, {{truncate subject 50}}
  • Defaults: {{default notifyuser_username "Unknown User"}}

Backward Compatibility

✅ Fully backward compatible — all existing {{variable}} syntax works unchanged
✅ No configuration changes required for existing webhook setups
✅ Existing webhook payloads continue to work without modification


Testing

Instructions to replicate my tests and test yourself

1. Setup Webhook.site

  1. Go to webhook.site
  2. Copy the unique URL they provide

2. Configure Webhook in Seerr

  1. Start the dev server with pnpm dev
  2. Navigate to http://localhost:5055/settings/notifications/webhook
  3. Enable the webhook
  4. Paste your test webhook URL
  5. Enable "Support Variables" toggle
  6. For the JSON payload to start, you can use something like:
{
  "type": "{{notification_type}}",
  "subject": "{{subject}}",
  "message": "{{message}}",
  "user": "{{notifyuser_username}}",
  "media_id": "{{media_tmdbid}}",
  "request_id": "{{request_id}}"
}
  1. Enable all the notification types so that we don't have to tweak those while testing
  2. Save the settings

3. Test Notifications

Click the Test button on the webhook settings page. This will:

  • Trigger a test notification through the webhook agent
  • Use the new Handlebars template engine to render variables
  • Send the webhook to your test endpoint

4. Verify Results

Check your webhook testing endpoint (webhook.site, etc.) and verify:

  1. The webhook was received
  2. Variables were replaced correctly (e.g., {{notification_type}}"TEST_NOTIFICATION")
  3. The JSON structure is valid
  4. No errors in the Seerr server logs

5. Test Advanced Handlebars Features and Edge Cases

Test these with actual requests:

Test Case 1: Handlebars Helpers

{
  "type": "{{notification_type}}",
  "subject": "{{subject}}",
  "user": {
    "name": "{{capitalize notifyuser_username}}",
    "email": "{{lower notifyuser_email}}"
  },
  {{#if media}}
  "media": {
    "id": "{{media_tmdbid}}",
    "type": "{{upper media_type}}",
    "status": "{{media_status}}"
  },
  {{/if}}
  {{#if request}}
  "request": {
    "id": "{{request_id}}",
    "requested_by": "{{requestedBy_username}}",
    "has_request": true
  },
  {{else}}
  "request": null,
  {{/if}}
  "timestamp": "{{default current_date 'No date'}}"
}

This tests:

  • capitalize helper
  • lower helper
  • upper helper
  • {{#if}}...{{/if}} conditionals
  • {{#if}}...{{else}}...{{/if}} with else branch
  • default helper

Test Case 2: Nested Conditionals

{
  "type": "{{notification_type}}",
  {{#if media}}
    {{#if request}}
  "media_request": {
    "media_id": "{{media_tmdbid}}",
    "request_id": "{{request_id}}"
  },
    {{/if}}
  {{/if}}
  "message": "{{subject}}"
}

Test Case 3: Truncate Helper

Find a long example for this. I used "Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb"

{
  "notification": "{{notification_type}}",
  "subject": "{{subject}}",
  "truncated_subject": "{{truncate subject 30}}",
  "user": "{{notifyuser_username}}",
  "comparison": {
    "original_length": "Full title shown above",
    "truncated_length": "Limited to 30 characters with ellipsis"
  }
}

Test Case 4: Comparison Helper

{
  "type": "{{notification_type}}",
  "checks": {
    "is_test": "{{#if (eq notification_type 'TEST_NOTIFICATION')}}yes{{else}}no{{/if}}",
    "has_media": "{{#if media}}yes{{else}}no{{/if}}"
  }
}

Test Case 5: Webhook URL with Variables

Change your URL to something like this:

https://webhook.site/YOUR-ID?type={{notification_type}}&user={{notifyuser_username}}

Manual Testing Completed

Manually tested with multiple scenarios:

  • ✅ Simple variable substitution
  • ✅ Conditionals with nested blocks
  • ✅ All custom helpers (capitalize, upper, lower, truncate, default, comparisons)
  • ✅ Webhook URL variable substitution
  • ✅ Auto-approved media requests (user fallback)
  • ✅ HTML character rendering

Future Work

See plan here #1883


Screenshot

N/A — Backend refactor (frontend validation updated but UI unchanged)


To-Dos

  • Disclosed any use of AI (see our [policy](../CONTRIBUTING.md#ai-assistance-notice)) — AI used (Cursor/Claude) for implementation assistance
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract — No new translation keys added
  • Database migration (if required) — Not required (uses existing settings system)

Issues Fixed or Closed

Part of the larger effort to enable custom email templates. This PR lays the groundwork for webhook template support that will be extended to all notification agents in future PRs.

…ating

Replaced custom KeyMap system with Handlebars template engine. Created TemplateEngine class with
custom helpers (comparison, logic, formatting). Updated frontend validation to support Handlebars
syntax. Added JSON cleanup for trailing commas from conditional rendering. Disabled HTML escaping
for proper character rendering. Added user fallback for auto-approved/auto-requested notifications.
Maintains full backward compatibility with existing {{variable}} syntax. Enables advanced templating
features (conditionals, loops, helpers). Foundation for extending template support to all
notification agents.
Comment on lines +145 to +149
if (hasHandlebarsContent) {
// For templates with Handlebars syntax, just check if it's not empty
// Backend will validate Handlebars syntax and provide error logs
return value.trim().length > 0;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More extensive validation would be if I imported Handlebars in the frontend to try test the render and see if we get errors but that would triple the bundle size for this page in my testing. If there are any other suggestions for simple frontend tests we should add, let me know

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant