OFeedback v2.0.0
Self-hosted feedback collection for WordPress — floating widget, inbox workflow, analytics, screenshot capture, Slack notifications, and a clean developer API. Your data never leaves your server.
What OFeedback does
A complete feedback loop in one plugin — collect, triage, notify, analyse, and export. No third-party service, no monthly fee, no data egress.
What you need
Getting installed
Via ZIP upload (recommended)
ofeedback.zip and click Install Now.Via FTP / file manager
ofeedback.zip to get the ofeedback/ folder.wp-content/plugins/.On activation
- The
wp_ofb_feedbackdatabase table is created (or upgraded viadbDeltaif an older version exists). - A daily WP-Cron event (
ofb_cleanup_screenshots) is scheduled to delete screenshot files from archived entries older than 30 days.
Clean uninstall
Deactivating the plugin does not delete your data. To remove everything permanently, deactivate first, then click Delete in Plugins.
WordPress calls uninstall.php automatically on delete, which:
wp_ofb_feedback table.ofb_* options from wp_options.ofb_cleanup_screenshots cron event.wp-content/uploads/ofb-screenshots/.Admin interface
A full-width 3-row sticky header — the WP sidebar is hidden on all OFeedback pages to prevent navigation conflicts.
| Row | Contents |
|---|---|
| Brand bar | OFeedback logo + icon, version badge, user avatar, light/dark theme toggle |
| Navigation | Inbox · Analytics · Settings |
| Contextual bar | Breadcrumb + contextual actions / filters |
Feedback inbox
The main submission table. Every piece of feedback flows through the New → Read → Archived status lifecycle.
Status tabs
| Tab | Description |
|---|---|
| New | Unread submissions — left-border accent on each row |
| Read | Viewed — status auto-sets to "read" on detail view open |
| Archived | Manually archived — screenshot files deleted 30 days after archiving |
| All | Every submission regardless of status |
Table columns
Checkbox · From (name + email) · Type tag · Message excerpt · Rating · Screenshot indicator · Page · Date · Status badge · Row actions
Filters & bulk actions
| Feature | Detail |
|---|---|
| Type filter | Dropdown matching enabled feedback types |
| Date range | From / To ISO date pickers |
| Search | Full-text across name, email, subject, message |
| CSV export | Button in topbar — downloads current type/status selection |
| Mark Read | Bulk action on checked rows |
| Archive | Bulk action on checked rows |
| Delete | Bulk action with confirmation prompt |
Submission detail view
Click View on any inbox row to open the full submission. Opening a "new" entry automatically marks it "read".
| Card | Contents |
|---|---|
| Message | Type tag, status badge, star rating, subject, full message body |
| Admin Notes | Private textarea saved per entry — never visible to submitters |
| Submitter sidebar | Name, email, user ID (if logged in), IP address |
| Context sidebar | Submission datetime, page title + URL, browser user agent |
| Reply card | Mailto link pre-filled with submitter email and subject (shown only when email is present) |
| Screenshot card | Thumbnail preview with download link; shows deletion date when archived |
Analytics tab
Server-rendered — no additional API calls. Pure CSS height-percentage charts, no JavaScript library required.
Stat cards
| Card | Data |
|---|---|
| Total Submissions | All-time count |
| This Month | Count since the 1st of the current month |
| Avg Rating | Average star rating across all rated submissions (0.0 if none) |
| Unread | Count with status = 'new' |
Charts
- Submissions by Type — horizontal bar chart per feedback type
- Rating Distribution — bars for each star value (1–5)
- Last 30 Days — vertical bar chart with one bar per day; missing days shown as zero
Frontend widget
A fixed-position button built and appended to <body> via JavaScript on page load (or after a trigger event).
Widget structure
Trigger modes
| Mode | Behaviour |
|---|---|
| Immediately | Widget appears as soon as the page loads |
| After a delay | Widget appears N seconds after page load (configurable) |
| After scroll depth | Widget appears after visitor scrolls X% of the page (5–95%) |
| Exit intent | Widget appears when cursor leaves the top of the viewport |
Auto-fill & exclusions
Logged-in users have their display_name and user_email pre-filled via wp_localize_script — no client-side fetch required.
Post/page IDs listed under Settings → Show/Hide Rules → Excluded Post IDs will not render the widget, even when globally enabled.
[ofeedback] shortcode
Embeds the full feedback form on any page or post — independent of the floating widget setting.
[ofeedback]
[ofeedback title="Give Us Feedback" description="We read every submission."]
| Attribute | Default | Description |
|---|---|---|
title | Feedback | Page heading |
description | Your thoughts help us improve. | Subheading text |
The form includes: name, email, type, subject, message (required), screenshot, and star rating. Add [ofeedback] to as many pages as you like — each renders an independent form.
Settings reference
| Option key | UI label | Type | Default |
|---|---|---|---|
ofb_notify_emails | Notification Recipients | Textarea | Site admin email |
ofb_send_confirmation | Send confirmation email to submitter | Checkbox | off |
ofb_slack_webhook | Slack Webhook URL | URL | — |
ofb_widget_enabled | Show floating widget | Checkbox | on |
ofb_widget_position | Button Position | Select | right |
ofb_widget_color | Button Color | Color + hex | #38BDF8 |
ofb_widget_label | Button Label | Text | Feedback |
ofb_widget_trigger | Trigger Mode | Select | immediate |
ofb_widget_delay_seconds | Delay (seconds) | Number | 3 |
ofb_widget_scroll_pct | Scroll depth (%) | Number 5–95 | 50 |
ofb_enabled_types | Enabled Feedback Types | Checkboxes | all |
ofb_screenshot_enabled | Allow screenshot attachment | Checkbox | on |
ofb_feedback_page_id | Linked Feedback Page | Page selector | — |
ofb_store_ip | Store IP address | Checkbox | on |
ofb_excluded_post_ids | Excluded Page/Post IDs | Comma-separated IDs | — |
Notifications overview
Every new submission triggers a single OFB_Mailer::send_all() call that fires the admin email, the optional Slack webhook, and the optional submitter confirmation in one shot.
Email notifications
On each new submission, an HTML email is sent to all configured notification recipients.
Recipients
Settings → Email Notifications → Notification Recipients. Enter one email per line, or comma-separated. Multiple recipients are supported.
Email contents
Type, name, email, subject, star rating, page link, full message body, and a View in OFeedback CTA button styled with the Orravo orange accent.
Submitter confirmation
When Send confirmation email to submitter is enabled and the submitter provides an email address, they receive a receipt email containing their own message.
Slack integration
Configure a Slack Incoming Webhook URL in Settings → Slack Integration. Leave the field blank to disable. Each new submission POSTs a formatted JSON payload to the webhook.
JSON{
"text": "*[Site Name] New Feedback — Bug / Technical Issue ★★★☆☆*\n*From:* Jane Doe\n>Login page throws a 500 error after password reset…\n<https://example.com/wp-admin/admin.php?page=ofb-feedback&view=detail&id=42|View in OFeedback admin>"
}
Screenshot capture
Client-side capture using html2canvas — loaded from jsDelivr CDN on demand only when the user clicks "Attach Screenshot". The ~300 KB library never affects initial page load.
Capture process
<canvas>, excluding the widget panel itself.ofb_upload_screenshot.wp-content/uploads/ofb-screenshots/, and returns the public URL.Cleanup & security
A daily WP-Cron job deletes screenshot files belonging to entries archived more than 30 days ago. Trigger manually at Settings → Screenshot Storage → Run Cleanup Now.
The ofb-screenshots/ directory is protected by a .htaccess file that disables directory listing and blocks PHP execution.
CSV export
One-click download from the Inbox topbar — filtered to the current type and status selection. UTF-8 with BOM for Excel compatibility. Direct browser download, no AJAX, no page reload.
| Export columns | |
|---|---|
| ID, Date, Status, Type | Subject, Message, Name, Email |
| Rating, Page Title, Page URL | IP Address, Admin Notes |
Developer API
One action hook, one filter hook, a set of PHP query functions, and guidance for adding your own REST endpoint.
Action hooks
ofeedback_submission_created
Fires immediately after a new submission is saved to the database, after email and Slack notifications have been sent.
PHPadd_action( 'ofeedback_submission_created', function( int $id, array $data ) {
// $id — The new row ID in wp_ofb_feedback
// $data — Sanitized submission array (type, message, name, email, rating, …)
error_log( 'New feedback #' . $id . ' from ' . ( $data['name'] ?: 'Anonymous' ) );
}, 10, 2 );
// Zapier / Make integration example
add_action( 'ofeedback_submission_created', function( $id, $data ) {
wp_remote_post( 'https://hooks.zapier.com/...', [
'body' => wp_json_encode( array_merge( ['id' => $id], $data ) ),
'headers' => ['Content-Type' => 'application/json'],
]);
}, 10, 2 );
Filter hooks
ofb_notify_email
Filters the notification recipient(s) before the admin email is sent. Receives the string of addresses (comma-separated or single).
PHPadd_filter( 'ofb_notify_email', function( string $email ): string {
return 'feedback@example.com, team@example.com';
} );
PHP functions
OFB_DB::query()
PHP$submissions = OFB_DB::query([
'status' => 'new', // '', 'new', 'read', 'archived'
'type' => 'bug', // '', 'general', 'bug', 'suggestion', …
'search' => 'login page', // full-text across name, email, subject, message
'date_from' => '2025-01-01', // Y-m-d
'date_to' => '2025-12-31', // Y-m-d
'orderby' => 'created_at', // id, name, email, type, rating, status, created_at
'order' => 'DESC', // ASC or DESC
'per_page' => 20,
'paged' => 1,
]);
// Returns array of stdClass objects
OFB_DB::get()
PHP$entry = OFB_DB::get( 42 );
if ( $entry ) {
echo $entry->name . ': ' . $entry->message;
}
Analytics methods
| Method | Returns |
|---|---|
OFB_DB::count_by_status( $status ) | Count of submissions matching status (blank = total) |
OFB_DB::analytics_by_type() | ['bug' => 12, 'general' => 8, …] ordered by count desc |
OFB_DB::analytics_by_rating() | [1 => 2, 2 => 5, 3 => 10, 4 => 18, 5 => 6] — all five stars, 0 if none |
OFB_DB::analytics_by_date( $days ) | ['Y-m-d' => count, …] — last N days, missing dates = 0 |
OFB_DB::average_rating() | Float — average star rating (0.0 if no rated submissions) |
OFB_Submission::types() | Full type list: ['general' => 'General Feedback', …] |
OFB_Submission::enabled_types() | Only types currently enabled in settings (falls back to all) |
REST endpoint
OFeedback does not ship a public REST endpoint by default. Add your own using the PHP functions above:
PHPadd_action( 'rest_api_init', function() {
register_rest_route( 'ofeedback/v1', '/submissions', [
'methods' => 'GET',
'callback' => function( WP_REST_Request $request ) {
if ( ! current_user_can('manage_options') ) {
return new WP_Error('forbidden', 'Insufficient permissions.', ['status' => 403]);
}
return rest_ensure_response( OFB_DB::query([
'status' => $request->get_param('status') ?? '',
'per_page' => min( 100, absint( $request->get_param('per_page') ?? 20 ) ),
'paged' => absint( $request->get_param('page') ?? 1 ),
]));
},
'permission_callback' => '__return_true',
]);
});
Database schema
Single table: wp_ofb_feedback (prefix varies). Indexes on status, type, archived_at, and created_at.
| Column | Type | Description |
|---|---|---|
id | BIGINT UNSIGNED AUTO_INCREMENT | Primary key |
type | VARCHAR(40) | Feedback type: general, bug, suggestion, etc. |
subject | VARCHAR(255) | Optional subject line |
message | TEXT | The feedback message (required) |
rating | TINYINT(1) UNSIGNED | Star rating 1–5, or NULL |
name | VARCHAR(150) | Submitter name (optional) |
email | VARCHAR(150) | Submitter email (optional) |
page_url | VARCHAR(500) | URL of the page where feedback was submitted |
page_title | VARCHAR(255) | Title of that page |
ip_address | VARCHAR(45) | Submitter IP (empty when ofb_store_ip is off) |
user_agent | VARCHAR(500) | Browser user agent string |
user_id | BIGINT UNSIGNED | WP user ID, or NULL for guests |
status | VARCHAR(20) | new, read, archived |
admin_notes | TEXT | Private notes by admins |
screenshot_url | VARCHAR(500) | Public URL to screenshot file |
screenshot_path | VARCHAR(500) | Absolute filesystem path (used for cleanup) |
archived_at | DATETIME | When the entry was archived (for cleanup scheduling) |
created_at | DATETIME | Submission timestamp |
updated_at | DATETIME | Last modification timestamp (auto-updated) |
Privacy & GDPR
Data collected per submission
- Message (required)
- Type, subject, rating (form fields)
- Name, email (optional — submitter-provided)
- Page URL and page title (from
window.locationanddocument.title) - IP address (optional — disable via Settings → Privacy)
- Browser user agent string
- WordPress user ID (if the submitter is logged in)
Compliance checklist
| Step | How |
|---|---|
| Disable IP storage | Settings → Privacy → uncheck Store IP address. Existing IPs are not retroactively deleted — export/delete first. |
| Privacy policy disclosure | Add OFeedback's data collection to your policy. Suggested text in the docs above. |
| Right to erasure | Delete individual submissions from the Inbox. No automated erasure tool. |
| Data location | All data stored in your own WordPress database. No data sent to Orravo or third parties (except optionally to Slack via the webhook you configure). |
| Screenshot files | Stored in wp-content/uploads/ofb-screenshots/ on your server. Auto-deleted 30 days after archiving. |
Frequently asked questions
.ofb-widget-trigger and .ofb-widget-panel.ofb-frontend-v2.js file is under 12 KB uncompressed. html2canvas (~300 KB) is only loaded when a user clicks "Attach Screenshot" — not part of the initial page load.[ofeedback] to as many pages as you like — each renders an independent form.archived. The screenshot file (if any) is queued for deletion 30 days later. The submission data itself remains in the database until manually deleted.wp_create_nonce call is inside wp_enqueue_scripts which runs per-request (not cached).ofeedback_submission_created action hook to POST to a webhook URL. See the Action Hooks section for the full example.What's changed
- NewAnalytics tab — stat cards, type breakdown, rating distribution, 30-day daily trend (CSS-only charts)
- NewSlack webhook integration — formatted JSON POST on each new submission
- NewMultiple notification email recipients (comma-separated or one per line)
- NewCSV export with date-range and type/status filtering
- NewAdmin bar unread badge (count of new submissions)
- NewWP admin sidebar hidden on plugin pages for clean full-width layout
- NewLogged-in user auto-fill for widget and page form
- NewWidget trigger modes: immediate, delay, scroll depth, exit intent
- NewEnabled types setting — restrict which feedback types appear in the form
- New
ofb_excluded_post_ids— hide widget on specific pages - New
ofb_store_ip— GDPR toggle for IP address storage - New
uninstall.php— clean table drop + options + cron + screenshot folder removal - New
ofeedback_submission_createdaction hook - New
OFB_DB::analytics_by_type/rating/date(),average_rating(),count_this_month() - Improved
OFB_DB::query()+query_count()acceptdate_from/date_toparams - ImprovedMailer uses
OFB_Mailer::send_all()— single call fires email + Slack + confirmation - ImprovedAdmin notification email uses Orravo-branded HTML (orange accent)
- FixedWP admin sidebar interference with plugin navigation
- NewRebranded from fideograph-feedback to OFeedback
- NewStandalone WP menu via
add_menu_page() - New3-row sticky header (brand bar + topnav + topbar)
- NewBulk actions: mark read, archive, delete
- NewDate filter and search in inbox
- NewAdmin notes per submission
- Newhtml2canvas client-side screenshot capture (replaced cron-based approach)
- NewScreenshot upload with magic-byte validation, 2 MB ceiling
- NewRate limiting (1 per IP per 5 min)
- NewDark/light theme toggle with localStorage persistence
- ImprovedStatus auto-set to "read" on detail view open
- NewSubmission confirmation email to submitter (optional)
Got a question about OFeedback?
Reach out directly — Kenneth replies within 24 hours.
