A secure and production-ready WordPress plugin for injecting custom JavaScript into the <head> and footer of your WordPress site.
| Feature | Free | Pro |
|---|---|---|
| Inline script editor (head + footer) | β | β |
| External script URL manager | β | β |
| Revision history & one-click rollback | β | β |
| Activity log | β | β |
| CodeMirror editor with autocomplete | β | β |
| Multisite compatible | β | β |
| Conditional loading (11 rule types, AND/OR) | β | β |
| Managed JS Files (create / edit / upload / delete) | β | β |
REST API (scriptomatic/v1, 14 endpoints) |
β | β |
WP-CLI (wp scriptomatic) |
β | β |
| API IP Allowlist (IPv4/IPv6/CIDR) | β | β |
| API Enable / Disable | β | β |
| API Allowed Users (restrict by admin account) | β | β |
| Email Notifications (always: site admin; opt-in: other admins) | β | β |
| Preferences Action History (read-only, paginated) | β | β |
β Upgrade to Scriptomatic Pro β
- π Security First: Comprehensive input validation, sanitization, secondary nonce system, and audit logging
- π€ Capability Checks: Only administrators with
manage_optionscan modify scripts - π Inline Script Editor: Full CodeMirror JavaScript editor with line numbers, bracket matching, and WordPress/jQuery-specific Ctrl-Space autocomplete. Falls back to a plain textarea if syntax highlighting is disabled in the user profile.
- π External Script URLs: Manage multiple remote
<script src>URLs per location with a chicklet UI; loaded before the inline block - π Revision History & Rollback: Every save stores a timestamped revision; restore any prior version in one AJAX click with no page reload
- π Activity Log: All script saves, rollbacks, and JS file events are recorded in a persistent Activity Log embedded at the bottom of each admin page. Inline script + conditions changes and external URL changes are recorded as separate independent entries, each with its own View and Restore buttons β the two never interfere with each other. Log limit is configurable in Preferences (3β1000, default 200).
- π Contextual Help: Built-in help tabs with detailed documentation on every admin page
- βΏ Accessibility: ARIA labels,
aria-describedby, and semantic fieldsets throughout - π Multisite Compatible: All script management is per-site; install/activate/deactivate network-wide; uninstall iterates every sub-site
- π§Ή Configurable Uninstall: Optionally retains or removes all data on deletion; fully multisite-aware
- π¬ Email Notifications: The site admin always receives a plain-text email for every script save, rollback, URL change, file save/delete, preferences save, and restore event. Other administrators can opt in via their WordPress profile page.
- π― Conditional Loading: Restrict injection to specific pages, post types, URL patterns, user login state, date ranges, date/time windows, ISO week numbers, or months β per inline script and per external URL (11 condition types, AND/OR stacked rules)
- ποΈ Managed JS Files: Create, edit, upload, and delete standalone
.jsfiles stored inwp-content/uploads/scriptomatic/; each file has its own Head/Footer selector, load conditions, and CodeMirror editor; files survive plugin updates - π REST API: Full
scriptomatic/v1REST API (all POST, WordPress Application Passwords). Fourteen endpoints cover inline scripts, external URL lists, managed JS files, and Preferences Action History β including a multipart file upload endpoint - π‘οΈ API IP Allowlist: Restrict REST API access to specific IPv4/IPv6 addresses or CIDR ranges from the Preferences page
- π API Enable / Disable: Toggle REST API access site-wide from Preferences; returns HTTP 503 when disabled
- π€ API Allowed Users: Restrict REST API access to named administrator accounts from Preferences; returns HTTP 403 for unlisted callers
- π» WP-CLI:
wp scriptomaticcommand group with subcommands for inline scripts, external URLs, managed JS files (includingfiles upload), activity log (log list), preferences management (prefs get,prefs set,prefs history), and history. All write commands share the same service layer as the REST API. Preferences management is CLI and Dashboard only β not REST API. - π€ JS File Upload: Upload a local
.jsfile from the JS Files list page, viaPOST /wp-json/scriptomatic/v1/files/upload, or withwp scriptomatic files upload --path=<file>
π‘ A 3-day free trial is available for all Pro features. A credit card or PayPal is required to start the trial.
- WordPress: 6.2 or higher (required for
%iidentifier placeholder in$wpdb->prepare()) - PHP: 7.2 or higher
- User Role: Administrator (manage_options capability)
The plugin uses a modular PHP-trait structure:
scriptomatic/
βββ scriptomatic.php # Entry point: header, constants, Freemius set_basename(), bootstrap
βββ freemius/ # Freemius SDK (bundled); do not edit
βββ assets/
β βββ admin.css # Admin stylesheet (enqueued via wp_enqueue_style)
β βββ admin.js # Admin JS (enqueued via wp_enqueue_script + wp_localize_script)
βββ includes/
βββ freemius-init.php # Freemius SDK bootstrap; scriptomatic_fs() + scriptomatic_is_premium()
β # helpers; uninstall cleanup hooked to Freemius after_uninstall
βββ class-scriptomatic.php # Singleton class β uses all eleven traits, registers hooks
βββ class-scriptomatic-cli.php# WP-CLI command class (loaded only when WP_CLI is defined) [Pro]
βββ trait-menus.php # Admin menu & submenu registration; help-tab hooks
βββ trait-sanitizer.php # Input validation and sanitisation
βββ trait-history.php # Revision history storage and AJAX rollback
βββ trait-settings.php # Settings API wiring and plugin-settings CRUD
βββ trait-renderer.php # Settings-field callbacks; load-condition evaluator
βββ trait-pages.php # Page renderers, Activity Log, JS Files pages, help tabs, action links
βββ trait-enqueue.php # Admin-asset enqueuing
βββ trait-injector.php # Front-end HTML injection
βββ trait-files.php # Managed JS files: CRUD, disk I/O, save + delete handlers [Pro]
βββ trait-api.php # REST API route registration, permission callbacks, service layer [Pro]
βββ trait-notifications.php # Email notifications (site admin always; others opt-in), Preferences Action History
All traits are included in class Scriptomatic via use, making cross-trait $this->method() calls work correctly.
- Download the latest release from GitHub
- Navigate to Plugins β Add New in your WordPress admin
- Click Upload Plugin and select the downloaded ZIP file
- Click Install Now and then Activate
- Download and extract the plugin files
- Upload the
scriptomaticfolder to/wp-content/plugins/ - Navigate to Plugins in WordPress admin
- Find Scriptomatic and click Activate
cd /path/to/wordpress/wp-content/plugins/
git clone https://github.com/richardkentgates/scriptomatic.gitThen activate via WordPress admin.
- Navigate to Scriptomatic β Head Scripts (or Footer Scripts) in your WordPress admin
- Enter your inline JavaScript in the textarea or add external script URLs via the URL manager
- Optionally configure Load Conditions (Pro) to restrict the script to specific pages, post types, URL patterns, user login state, date ranges, datetime windows, week numbers, or months
- Click Save Head Scripts (or Save Footer Scripts)
- Your code will be automatically injected into the
<head>or just before</body>depending on which page you used
| Page | Path | Purpose |
|---|---|---|
| Head Scripts | Scriptomatic β Head Scripts | Inline JS + external URLs injected in <head>; includes Activity Log |
| Footer Scripts | Scriptomatic β Footer Scripts | Inline JS + external URLs injected before </body>; includes Activity Log |
| JS Files (Pro) | Scriptomatic β JS Files | Create, edit, and delete managed .js files; each file has its own Head/Footer toggle, load conditions, and CodeMirror editor; list view and edit view each include an Activity Log panel |
| Preferences | Scriptomatic β Preferences | Activity log limit (3β1000), save confirmation toggle, uninstall data retention, API Allowed IPs (Pro), API Enable/Disable (Pro), API Allowed Users (Pro), email notification opt-ins (per profile), and a read-only Preferences Action History (last 100 entries, 20/page, AJAX pagination) |
- Do NOT include
<script>tags β they are added automatically for inline code - Scripts are injected on pages matching the configured Load Conditions (defaults to all pages)
- External script URLs are loaded via
<script src="...">tags; inline code is wrapped in<script>tags - Revision history is maintained per location (head and footer independently)
- Test thoroughly before deploying to production
- Use the Help tab in the admin for detailed guidance
// Google Analytics tracking code
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');// Facebook Pixel
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'YOUR_PIXEL_ID');
fbq('track', 'PageView');// Custom initialization
jQuery(document).ready(function($) {
console.log('Scriptomatic loaded successfully!');
// Your custom code here
$('.my-element').on('click', function() {
alert('Element clicked!');
});
});All endpoints are POST. Authentication uses WordPress Application Passwords:
Authorization: Basic base64(username:application-password)
Base URL: /wp-json/scriptomatic/v1/
Pro feature. REST API access requires an active Scriptomatic Pro licence. A 3-day free trial is available (credit card or PayPal required).
API Controls (Pro) in Preferences:
- API Enable / Disable β uncheck to disable the REST API site-wide; blocked requests receive
503 rest_api_disabled. - API Allowed IPs β restrict access to specific IPv4 addresses, IPv6 addresses, or IPv4 CIDR ranges (one per line). Leave empty to allow all IPs. Blocked requests receive
403 rest_ip_forbidden. - API Allowed Users β restrict access to named administrator accounts. Callers not on the list receive
403 rest_user_forbidden. Leave all boxes unchecked to allow any administrator.
| Endpoint | Required params | Optional params | Description |
|---|---|---|---|
/script |
location |
β | Get current inline script |
/script/set |
location, content |
conditions (JSON) |
Save inline script |
/script/rollback |
location, id (DB row ID) |
β | Restore script snapshot |
/history |
location |
β | List inline script history |
/urls |
location |
β | Get external URL list |
/urls/set |
location, urls (JSON array) |
β | Replace external URL list |
/urls/rollback |
location, id (DB row ID) |
β | Restore URL snapshot |
/urls/history |
location |
β | List URL history |
/prefs/history |
β | limit (1β100, default 20), offset |
List Preferences Action History (read-only) |
/files |
β | β | List all managed JS files |
/files/get |
file_id |
β | Get file content + metadata |
/files/set |
label, content |
file_id, filename, location, conditions |
Create or update a file |
/files/delete |
file_id |
β | Delete a managed JS file |
/files/upload |
multipart file field |
label, file_id, location, conditions |
Upload a .js file |
location is "head" or "footer". id is the DB row primary key of the snapshot to restore β obtain IDs from the history endpoints. All write operations share the same service layer as the admin UI β identical validation and activity logging apply.
# Example: get current head script
curl -X POST https://example.com/wp-json/scriptomatic/v1/script \
-H "Authorization: Basic $(echo -n 'admin:xxxx xxxx xxxx xxxx xxxx xxxx' | base64)" \
-H "Content-Type: application/json" \
-d '{"location":"head"}'
# Example: upload a .js file
curl -X POST https://example.com/wp-json/scriptomatic/v1/files/upload \
-H "Authorization: Basic $(echo -n 'admin:xxxx xxxx xxxx xxxx xxxx xxxx' | base64)" \
-F "file=@/path/to/tracker.js" \
-F "label=My Tracker" \
-F "location=head"Pro feature. WP-CLI commands require an active Scriptomatic Pro licence. A 3-day free trial is available (credit card or PayPal required).
All commands are in the wp scriptomatic group. Write commands share the same service layer as the REST API and admin UI. Preferences management (prefs get, prefs set, prefs history) is available via CLI only β not the REST API.
# Get current inline script
wp scriptomatic script get --location=<head|footer>
# Set inline script (from string or file)
wp scriptomatic script set --location=<head|footer> [--content=<js>] [--file=<path>] [--conditions=<json>]
# Rollback to a snapshot (use `wp scriptomatic history` to get the ID)
wp scriptomatic script rollback --location=<head|footer> --id=<id>
# List inline script history
wp scriptomatic history --location=<head|footer> [--format=<table|json|csv|yaml|count>]# Get current external URL list
wp scriptomatic urls get --location=<head|footer> [--format=<format>]
# Replace external URL list (JSON array of {url, conditions} objects)
wp scriptomatic urls set --location=<head|footer> (--urls=<json> | --file=<path>)
# Rollback URL list to a snapshot (use `wp scriptomatic urls history` to get the ID)
wp scriptomatic urls rollback --location=<head|footer> --id=<id>
# List URL history
wp scriptomatic urls history --location=<head|footer> [--format=<format>]# List all managed JS files
wp scriptomatic files list [--format=<table|json|csv|yaml|count>]
# Get file content + metadata
wp scriptomatic files get --id=<file-id> [--format=<table|json>]
# Create or update a file
wp scriptomatic files set --label=<label> (--content=<js> | --file=<path>) \
[--id=<file-id>] [--filename=<fn>] [--location=<head|footer>] [--conditions=<json>]
# Upload a local .js file
wp scriptomatic files upload --path=<local-path> \
[--label=<label>] [--id=<file-id>] [--location=<head|footer>] [--conditions=<json>]
# Delete a managed JS file
wp scriptomatic files delete --id=<file-id> [--yes]# List activity log (all locations, or scoped to one)
wp scriptomatic log list [--location=<head|footer|file|all>] [--limit=<n>] [--format=<table|json|csv|yaml|count>]Preferences management is available from the Dashboard and WP-CLI only β intentionally absent from the REST API.
# Display all current preferences
wp scriptomatic prefs get
wp scriptomatic prefs get --format=json
# Set a preference value
wp scriptomatic prefs set --key=max_log_entries --value=500
wp scriptomatic prefs set --key=keep_data_on_uninstall --value=true
wp scriptomatic prefs set --key=save_confirm_enabled --value=false
wp scriptomatic prefs set --key=api_enabled --value=true # Pro
wp scriptomatic prefs set --key=api_allowed_ips --value="203.0.113.0/24,192.0.2.1" # Pro
wp scriptomatic prefs set --key=api_allowed_users --value="admin,editor2" # Pro
# View preferences change history
wp scriptomatic prefs history
wp scriptomatic prefs history --format=jsonValid keys for prefs set:
| Key | Type | Notes |
|---|---|---|
max_log_entries |
integer 3β1000 | Maximum number of activity log entries to retain |
keep_data_on_uninstall |
bool | Preserve all plugin data when uninstalling (default: false) |
save_confirm_enabled |
bool | Show a confirmation dialog before saving changes |
api_enabled |
bool | Enable or disable the REST API site-wide (Pro) |
api_allowed_ips |
string | Newline- or comma-separated IPv4/IPv6/CIDR ranges; leave empty to allow all IPs (Pro) |
api_allowed_users |
string | Comma-separated administrator logins or user IDs; leave empty to allow all administrators (Pro) |
--conditions accepts a JSON string: '{"logic":"and","rules":[{"type":"front_page","values":[]}]}'
--format defaults to table. Use the history commands to look up --id values for rollback.
Scriptomatic is built with security as a top priority:
- Maximum content length enforced (100KB)
- Automatic removal of
<script>tags to prevent double-wrapping - Detection of potentially dangerous HTML tags (iframe, object, embed, link, style, meta)
- Invalid UTF-8 and control characters are rejected
- Input sanitization using WordPress core functions
- Admin notices for failed validation checks and automatic cleanup
- Restricts access to users with
manage_optionscapability (Administrators) - Nonce verification on all form submissions β both the WordPress Settings API nonce and a secondary location-specific nonce
- Capability checks on every admin page load
- All saves, AJAX rollbacks, and JS file events are recorded in the persistent Activity Log embedded on each admin page; each page shows only its own location's entries
- Every log entry includes a source field (
dashboard,api,cli) recording the originating channel; the Via column is shown in CLI history output - Inline script + conditions changes and external URL changes are written as separate entries β each with its own View/Restore buttons and rollback path; restoring one never touches the other
- Each entry captures: timestamp, username, user ID, action (
save,url_save,rollback,url_rollback,file_save,file_rollback,file_delete,file_restored), source, and a human-readable summary of what changed - The Restore button is disabled on the most recent entry of each dataset (it already reflects the live state)
- Entries with content snapshots expose View and Restore buttons directly in the table
- No IP addresses collected (intentional privacy decision)
- Log limit is configurable (3β1000, default 200 entries); oldest entries are discarded automatically once the cap is reached
- Helps track changes and detect unauthorised modification
- Email Notifications: The site admin always receives a plain-text email every time any script, URL, file, preferences, or rollback event is written to the Activity Log; emails include a
Via: Dashboard/API/CLIline. Other administrators can opt in via their WordPress profile page.
- Proper escaping of all admin interface text
- Content validated before injection
- Scripts only injected on front-end (not in admin)
- Uninstall behaviour is controlled by the Keep data on uninstall setting in Preferences; data is removed by default unless the setting is enabled
- Uninstall cleanup is hooked to Freemius's
after_uninstallaction, ensuring opt-out feedback is reported before data is removed - On multisite, uninstall iterates every sub-site and removes per-site option data, then cleans network-level options
- Multisite-aware data handling
- Freemius SDK (bundled,
vendor/freemius/): handles licence management, in-dashboard upgrade flow, and uninstall opt-out survey. Freemius makes outbound requests tofreemius.comfor licence validation and telemetry (collected only with user opt-in). No other external calls are made by the plugin itself. - No raw SQL from user input β all custom-table queries use
$wpdb->prepare()with typed placeholders (%i,%d,%s)
- Verify the Source: Only use code from trusted sources
- Test in Staging: Always test in a staging environment first
- Keep Backups: Save a copy of your script before making changes
- Document Your Code: Add comments explaining what the script does
// β
Good: Well-documented, clean code
// Google Analytics - Added 2026-02-26 by Admin
(function() {
'use strict';
// Your code here
})();
// β Bad: Undocumented, messy code
eval(someUntrustedString); // Never use eval!- Use Async Loading: Load external scripts asynchronously when possible
- Minimize Code: Remove unnecessary whitespace and comments for production
- Monitor Impact: Use browser dev tools to check performance impact
- Conditional Loading: Use the built-in Load Conditions feature to restrict scripts to only the pages that need them β head and footer each have independent conditions
- Never Use
eval(): Avoid eval() and similar dangerous functions - Validate URLs: Ensure external script URLs use HTTPS
- Review Regularly: Audit your scripts periodically
- Keep Updated: Stay informed about security best practices
Problem: Code doesn't show in page source
Solutions:
- Verify you clicked Save Head Scripts or Save Footer Scripts
- Clear WordPress and browser cache
- Check if theme calls
wp_head()properly - Disable other plugins to check for conflicts
Problem: Console shows errors
Solutions:
- Check browser console for specific error messages
- Validate JavaScript syntax using a linter
- Ensure external resources are loading (check Network tab)
- Test with simple
console.log('test')first
Problem: Save button doesn't work or changes appear to be ignored
Solutions:
- Verify you have administrator privileges
- Check if the inline script exceeds the 100 KB limit (JS files are limited by the server's upload setting, not this plugin)
- Remove any HTML tags (JavaScript only)
- Check browser console for JavaScript errors
Problem: A script change introduced an error and you need to roll back
Solutions:
- Scroll to the Activity Log panel at the bottom of the Head Scripts, Footer Scripts, or JS Files edit page
- Click Restore next to any saved revision to instantly roll back via AJAX β no further Save needed
- For inline script entries, Restore writes the script content and load conditions back; for URL entries, Restore writes back the external URLs as they were β each is restored independently
- For JS files, Restore writes the snapshot directly to disk
- The Restore button is disabled on the most recent entry of each dataset (already the current state)
Problem: Site is slower after adding script
Solutions:
- Use async attributes for external scripts
- Minimize script size
- Consider conditional loading
- Use performance monitoring tools
- Changelog: Version history and changes
- Architecture: Internal structure reference for developers extending the plugin
- Security Policy: Security guidelines and reporting
- License: GPL v2 license details
The plugin source code is licensed under the GNU General Public License v2 or later.
Copyright (C) 2026 Richard Kent Gates
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
See LICENSE file for full license text.
Pro features (conditional loading, managed JS files, REST API, WP-CLI, IP allowlist) require a separate commercial licence purchased through Freemius. The commercial licence grants access to Pro functionality, automatic updates, and support. It does not alter the GPL v2+ terms that apply to the source code.
Richard Kent Gates
- Website: richardkentgates.com
- GitHub: @richardkentgates
If you find this plugin helpful, please:
- β Star this repository
- π Report bugs via GitHub Issues
- π‘ Suggest features or improvements
- Share with others who might benefit
- Plugin Homepage: https://github.com/richardkentgates/scriptomatic
- Issue Tracker: https://github.com/richardkentgates/scriptomatic/issues
- Documentation: README.md
- Upgrade to Pro: https://checkout.freemius.com/mode/dialog/plugin/25187/plan/
See CHANGELOG.md for a detailed version history.
This plugin allows you to inject arbitrary JavaScript code into your website. While we provide security measures, it is your responsibility to ensure that any code you add is safe, secure, and does not violate any terms of service or laws. Always test thoroughly and only use code from trusted sources.
Made with β€οΈ by Richard Kent Gates
