{"id":359065,"date":"2026-08-26T07:06:47","date_gmt":"2026-08-26T07:06:47","guid":{"rendered":"https:\/\/en-ca.wordpress.org\/plugins\/keel-defaults\/"},"modified":"2026-08-26T07:06:14","modified_gmt":"2026-08-26T07:06:14","slug":"keel-defaults","status":"publish","type":"plugin","link":"https:\/\/mfe.wordpress.org\/plugins\/keel-defaults\/","author":240675,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"0.5.9","stable_tag":"0.5.9","tested":"7.1","requires":"6.4","requires_php":"7.4","requires_plugins":null,"header_name":"Keel Defaults","header_author":"Dan Knauss","header_description":"More than 30 sane WordPress defaults, each one a switch you can see and turn off \u2014 security, updates, privacy, UX, and performance.","assets_banners_color":"d2d2d2","last_updated":"2026-08-26 07:06:14","external_support_url":"","external_repository_url":"","donate_link":"https:\/\/github.com\/sponsors\/dknauss","header_plugin_uri":"https:\/\/github.com\/dknauss\/keel","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":0,"downloads":35,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.5.9":{"tag":"0.5.9","author":"dpknauss","date":"2026-08-26 07:06:14"}},"upgrade_notice":{"0.5.9":"<p>Removing the plugin now fully clears breach-cache data on sites using Redis or Memcached. Help text rewritten; no setting changes.<\/p>","0.5.8":"<p>Formatting fix in the Help menu. No behaviour changes.<\/p>","0.5.7":"<p>Fixes breach-screening status being reported from missing evidence, dependent XML-RPC controls not updating without a reload, and locked sliders accepting edits. Recommended if you use any of those.<\/p>","0.5.6":"<p>Breach-screening outages are now reported under Site Health rather than passing unnoticed. No change to what is or is not allowed as a password.<\/p>","0.5.5":"<p>Conflict reports no longer print an internal marker where a plugin name would go, and the dashboard notice now agrees with Site Health about how many settings are affected.<\/p>","0.5.4":"<p>Warnings about a setting being overridden by another plugin now reach the dashboard rather than only Site Health, and a comments heading no longer appears on posts with comments off.<\/p>","0.5.3":"<p>Internal change to how Keel&#039;s own CSS and JavaScript reach the page; no setting changes and nothing looks different. The Canadian English catalog is no longer bundled \u2014 translations now come from translate.wordpress.org like every other locale.<\/p>","0.5.2":"<p>On WordPress 6.4\u20136.9 an earlier release could switch the stored AI Connectors value off when you saved any setting. It has no effect before WordPress 7.0, where the control appears under Settings \u2192 Keel. To correct it now: <code>wp option patch update keel_settings disable_ai_connectors yes<\/code><\/p>","0.5.1":"<p>Policy-overlap diagnostics no longer execute other plugins&#039; callbacks. Re-check prior 0.5.0 conflict results; 0.5.1 reports structural overlap without claiming the configured outcomes disagree.<\/p>","0.5.0":"<p>Existing sites keep unlimited revision history unless you choose a limit; new activations default to 10. Re-check overlapping-policy results because Keel now reports only confirmed incompatible effects as actionable.<\/p>","0.4.1":"<p>Fixes the overlapping-settings check naming plugins that were not competing. If 0.4.0 told you another plugin was setting the same things and you have not acted on it yet, re-check under Site Health before deactivating anything. Confirmed results were always correct; the unconfirmed ones are gone.<\/p>","0.4.0":"<p>From wordpress.org: nothing to do, and your settings are kept. From GitHub before this release: the folder changed from <code>keel<\/code> to <code>keel-defaults<\/code>, so WordPress sees a new plugin rather than an update. Deactivate and delete the old copy \u2014 your settings are stored separately and survive it.<\/p>","0.3.0":"<p>Nothing to do on upgrade: no setting changes meaning and no stored value is rewritten. Multisite networks gain a Network Admin screen that can set any default for every site; it does nothing until a Super Admin uses it.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3666401,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3666401,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3666401,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3666401,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3666401,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.5.9"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3666401,"resolution":"1","location":"assets","locale":"","width":1280,"height":900},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3666401,"resolution":"2","location":"assets","locale":"","width":1078,"height":500},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3666401,"resolution":"3","location":"assets","locale":"","width":1280,"height":881}},"screenshots":{"1":"Settings \u2192 Keel. Every default is one switch with the reason it exists written beside it, so nothing the plugin does is hidden behind a name you have to guess at.","2":"The Passwords help tab. Length and breach screening in place of composition rules, with what the breach check actually sends spelled out \u2014 five characters of a hash, never the password.","3":"Site Health \u2192 Info. Every default and its current state on one read-only screen, so you can answer \"what is this plugin doing to my site?\" without opening the settings and reading checkboxes."}},"plugin_section":[],"plugin_tags":[20705,31093,247,396,600],"plugin_category":[54],"plugin_contributors":[261826],"plugin_business_model":[],"class_list":["post-359065","plugin","type-plugin","status-publish","hentry","plugin_tags-defaults","plugin_tags-hardening","plugin_tags-performance","plugin_tags-privacy","plugin_tags-security","plugin_category-security-and-spam-protection","plugin_contributors-dpknauss","plugin_committers-dpknauss"],"banners":{"banner":"https:\/\/ps.w.org\/keel-defaults\/assets\/banner-772x250.png?rev=3666401","banner_2x":"https:\/\/ps.w.org\/keel-defaults\/assets\/banner-1544x500.png?rev=3666401","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/keel-defaults\/assets\/icon.svg?rev=3666401","icon":"https:\/\/ps.w.org\/keel-defaults\/assets\/icon.svg?rev=3666401","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/keel-defaults\/assets\/screenshot-1.png?rev=3666401","caption":"Settings \u2192 Keel. Every default is one switch with the reason it exists written beside it, so nothing the plugin does is hidden behind a name you have to guess at."},{"src":"https:\/\/ps.w.org\/keel-defaults\/assets\/screenshot-2.png?rev=3666401","caption":"The Passwords help tab. Length and breach screening in place of composition rules, with what the breach check actually sends spelled out \u2014 five characters of a hash, never the password."},{"src":"https:\/\/ps.w.org\/keel-defaults\/assets\/screenshot-3.png?rev=3666401","caption":"Site Health \u2192 Info. Every default and its current state on one read-only screen, so you can answer \"what is this plugin doing to my site?\" without opening the settings and reading checkboxes."}],"raw_content":"<!--section=description-->\n<p>Keel flips a menu of sensible defaults onto any WordPress install, each one a switch under <strong>Settings \u2192 Keel<\/strong>. Nothing is hidden and nothing is all-or-nothing \u2014 you can see exactly what the plugin does to your site and turn any piece off.<\/p>\n\n<p>All 39 defaults are declared in a single schema array that drives both the settings screen and the code that wires them to WordPress. A default is an opinionated filter behind a control.<\/p>\n\n<p><strong>Disabling something means it is actually disabled.<\/strong> When you switch comments off, they are off below the presentation layer, not merely hidden by the theme template and the REST route \u2014 ask the database directly with <code>get_comments()<\/code> and there is nothing to hand back. The same care runs through the rest: closing the REST API also removes the link advertising it, and disabling comments also stops the comment feed answering.<\/p>\n\n<p><strong>Site Health shows you the whole posture<\/strong>, read-only: every default and its current state on one screen, so you can see what the site is actually doing without clicking through tabs. It also reports when another plugin is controlling the same settings, which otherwise fails silently.<\/p>\n\n<p><strong>Outgoing email stops at the edge of production.<\/strong> A database copied down from production carries real customer addresses and whatever mail service production was using, so a cron run or a bulk action can email real people from a staging site or a laptop. Keel suppresses outgoing mail on any environment that is not production \u2014 on by default, does nothing on production, and says so in an admin notice so nobody is left wondering why a password reset never arrived.<\/p>\n\n<p><strong>It works the same on a network.<\/strong> Activated across multisite, Keel seeds every existing site and every site created afterwards, so a later change to a default cannot move some sites and not others. A Super Admin can decide any setting for the whole network under Network Admin \u2192 Settings \u2192 Keel Defaults; sites see those settings as locked, with their own saved values untouched underneath, so lifting a policy returns each site to exactly what it had.<\/p>\n\n<h3>External services<\/h3>\n\n<p>When the <strong>Require strong passwords<\/strong> default is enabled, Keel screens new passwords against the <strong>Have I Been Pwned<\/strong> Pwned Passwords range API (<code>https:\/\/api.pwnedpasswords.com<\/code>) to reject passwords found in known breaches. This uses k-anonymity: only the first five characters of the password's SHA-1 hash are ever sent \u2014 never the password, and never the full hash. No personal data is transmitted. The check runs only when a password is being set or changed and the default is on. It can be disabled with <code>define( 'KEEL_DISABLE_HIBP', true );<\/code> in <code>wp-config.php<\/code>, with the <code>keel_disable_hibp<\/code> filter, or by turning off the strong-password default. If the API is unreachable, or answers with a truncated or malformed response, the check is skipped and the password is allowed \u2014 a breach-data outage never blocks a password change. It is not skipped silently: the failure is recorded and reported under Site Health, so a site whose screening has stopped working can tell. Only the kind of failure and when it happened are stored \u2014 never the password, and never the hash prefix. Have I Been Pwned is operated by Troy Hunt; see https:\/\/haveibeenpwned.com\/Privacy and https:\/\/haveibeenpwned.com\/API\/v3 for its terms and privacy policy.<\/p>\n\n<h3>Recommended wp-config.php hardening<\/h3>\n\n<p>A few defences live best in <code>wp-config.php<\/code>, outside any plugin: they apply before plugins load and cannot be switched off from the dashboard. These are optional and independent of Keel \u2014 add the ones that fit your site.<\/p>\n\n<pre><code>define( 'DISALLOW_FILE_EDIT', true ); \u2014 removes the built-in plugin and theme code editors, so a compromised admin account cannot edit PHP from the dashboard.\n\ndefine( 'WP_POST_REVISIONS', 10 ); \u2014 caps stored post revisions so the database does not grow without bound. Keel's **Post Revision Retention** control can govern the same policy after plugins load; a numeric or false constant remains the higher-level operator choice and locks that control.\n\ndefine( 'AUTOSAVE_INTERVAL', 120 ); \u2014 lengthens the editor autosave interval. This is independent of Keel's Heartbeat throttle: both influence how often the editor saves in the background, but neither replaces or overrides the other.&lt;h3&gt;Credits&lt;\/h3&gt;\n<\/code><\/pre>\n\n<p>Keel is a de-branded evolution of Better by Default, the WordPress defaults plugin by WPYEG (the Edmonton WordPress meetup): https:\/\/github.com\/WPYEG\/Better-by-Default<\/p>\n\n<p>Better by Default is published under the GPL-3.0-or-later; its sole author, who also wrote Keel, additionally licenses the portions carried over here under the GPL-2.0-or-later. Keel keeps Better by Default's core architecture \u2014 a single schema array that drives both the settings screen and the bootstrap, where each default is one array entry plus one hook \u2014 and adds further hardening and admin defaults adapted from the Pixel Managed Platform plugin (GPL-2.0-or-later).<\/p>\n\n<p>Pixel Managed Platform is itself a hard fork of the 10up Experience plugin by 10up (GPL-2.0-or-later): https:\/\/github.com\/10up\/10up-experience \u2014 so several of Keel's adapted defaults ultimately descend from code first written for 10up Experience. Copyright in that work is retained by 10up and its contributors, and 10up retains its marks; Keel is not affiliated with or endorsed by 10up. See LICENSE for the full GPL-2.0 text.<\/p>\n\n<h3>Support This Plugin<\/h3>\n\n<p>Keel is free and stays free. If it saves you an afternoon of hardening a new site, or keeps a staging server from emailing your client's customers, you can support its maintenance through <a href=\"https:\/\/github.com\/sponsors\/dknauss\">GitHub Sponsors<\/a>.<\/p>\n\n<p>Bug reports and feature requests are welcome on the issue tracker: <a href=\"https:\/\/github.com\/dknauss\/keel\/issues\">https:\/\/github.com\/dknauss\/keel\/issues<\/a>. If you have found a security problem, please report it privately rather than in a public issue \u2014 SECURITY.md ships with the plugin and says how.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Copy the plugin folder into <code>wp-content\/plugins\/<\/code>, or upload the built zip through <strong>Plugins \u2192 Add New \u2192 Upload Plugin<\/strong>.<\/li>\n<li>Activate it. The documented defaults are seeded on activation; nothing is applied before that.<\/li>\n<li>Visit <strong>Settings \u2192 Keel<\/strong> and turn off anything you do not want.<\/li>\n<\/ol>\n\n<p>Every default is a switch, and the switches are the whole interface. Defaults that can change behaviour or break an integration \u2014 requiring authentication for all REST requests, blocking the XML-RPC endpoint, the Classic editor \u2014 are off out of the box and opt-in.<\/p>\n\n<p>There is one exception: Keel sends an <code>X-Frame-Options<\/code> header of <code>SAMEORIGIN<\/code>, so other sites cannot embed yours in an iframe. If something else is meant to display this site inside a frame \u2014 an intranet dashboard, a screenshot or visual-review service, a kiosk or signage screen \u2014 set <strong>Frame options<\/strong> to \"Leave unchanged\" under Security and Attack Surface. A blocked frame usually fails silently, as a blank box.<\/p>\n\n<p>Deactivating stops every default at once; stored settings are kept so reactivating restores the same configuration. Uninstalling removes them.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"what%20changes%20when%20i%20activate%20it%3F\"><h3>What changes when I activate it?<\/h3><\/dt>\n<dd><p>Sixteen of the thirty-nine defaults are on out of the box, and nine more settings that are not simple switches apply a starting value. Nothing is written to your content and nothing is deleted; every one of them is a switch on <strong>Settings \u2192 Keel<\/strong> you can turn off, and turning it off puts WordPress back exactly as it shipped.<\/p>\n\n<p>Most of it is quiet. Users stop being listed to anonymous REST requests, new passwords have to be long and must not appear in a known breach, raw HTML and JavaScript are limited to Administrators, baseline security headers are sent, AI provider connectors are switched off, translations keep auto-updating, uploads get lowercase filenames, attachment screens show which image sizes were generated, and the site warns you if its own email looks misconfigured.<\/p>\n\n<p>Three are visible straight away and are the ones to know about. <strong>Comments, trackbacks and pingbacks are switched off<\/strong> everywhere, including for existing posts \u2014 nothing is deleted, and turning the setting off brings every comment back. <strong>Author archives stop resolving<\/strong>, so <code>\/author\/name\/<\/code> no longer returns a page. And <strong><code>X-Frame-Options: SAMEORIGIN<\/code> is sent<\/strong>, which stops other sites displaying yours in an iframe; if something is meant to embed this site, set <strong>Frame options<\/strong> to \"Leave unchanged\".<\/p>\n\n<p>Two more change things you may not see immediately: attachment pages redirect to the parent post, and self-pingbacks and the emoji detection script are gone.<\/p>\n\n<p>The starting values are conservative. Core auto-updates are set to <strong>minor<\/strong> \u2014 maintenance and security releases install themselves, major versions do not \u2014 ten post revisions are kept, logins last two days or fourteen with \"Remember me\", and subscribers are exempt from the password rules. The admin menu width, the front-end admin bar and the login logo are all left as WordPress has them until you choose otherwise.<\/p>\n\n<p>One default is on but does nothing on a live site: <strong>outgoing email is blocked on any environment that is not production<\/strong>, so a database copied to staging or a laptop cannot email real people. On production it never acts.<\/p><\/dd>\n<dt id=\"will%20this%20break%20my%20site%3F\"><h3>Will this break my site?<\/h3><\/dt>\n<dd><p>The defaults that are on out of the box are low-risk, with one exception worth naming: <code>X-Frame-Options: SAMEORIGIN<\/code> is sent by default, and it stops other sites embedding yours in an iframe. Set <strong>Frame options<\/strong> to \"Leave unchanged\" if the site is meant to be embedded, because a blocked frame fails silently as a blank box.<\/p>\n\n<p>Everything else that can break something is off and opt-in, and each says on the settings screen what it will cost you \u2014 for example that blocking the XML-RPC endpoint also stops apps and services that publish through it. Requiring authentication for REST is the one place Keel spends a little of that strictness back: <code>oembed\/1.0<\/code> stays reachable, so other sites can still embed your posts when every other route is closed.<\/p><\/dd>\n<dt id=\"does%20it%20send%20anything%20off%20my%20site%3F\"><h3>Does it send anything off my site?<\/h3><\/dt>\n<dd><p>One thing, and only when the strong-password default is on: the first five characters of a password's SHA-1 hash, to check it against known breaches. Never the password, never the full hash, no personal data. See <strong>External services<\/strong> above for the full description and how to switch it off.<\/p><\/dd>\n<dt id=\"why%20has%20email%20stopped%20working%20on%20my%20staging%20site%3F\"><h3>Why has email stopped working on my staging site?<\/h3><\/dt>\n<dd><p>Because Keel switched it off, deliberately, and there is an admin notice on the site saying so. The <strong>Non-Production Email<\/strong> default suppresses outgoing mail on any environment that is not production, so a database copied down from production cannot email real customers from a staging site or a laptop.<\/p>\n\n<p>It does nothing on production, so it cannot be left on by mistake. To send from a non-production site anyway, turn the default off under <strong>Settings \u2192 Keel<\/strong>, define <code>KEEL_ALLOW_NONPRODUCTION_MAIL<\/code> in <code>wp-config.php<\/code>, or use the <code>keel_suppress_nonproduction_mail<\/code> filter. A mail catcher can still record what would have been sent by hooking <code>keel_outgoing_mail_suppressed<\/code>.<\/p>\n\n<p>The environment is read the same way the admin-bar environment indicator reads it: <code>WP_ENVIRONMENT_TYPE<\/code>, whether set as a constant or an environment variable, and a host-name fallback for local development tools when neither is set.<\/p><\/dd>\n<dt id=\"does%20it%20delete%20anything%3F\"><h3>Does it delete anything?<\/h3><\/dt>\n<dd><p>No. Disabling comments hides them and closes the forms; nothing is removed from the database, and turning the default off brings every comment back. The same holds for the other content defaults.<\/p><\/dd>\n<dt id=\"can%20i%20set%20these%20in%20code%20instead%3F\"><h3>Can I set these in code instead?<\/h3><\/dt>\n<dd><p>Yes. Every default reads its value through the plugin's own option, and the behaviours are filterable \u2014 <code>keel_weak_roles<\/code>, <code>keel_disable_hibp<\/code>, <code>keel_comment_blocks<\/code>, <code>keel_allowed_comment_types<\/code> and others. A <code>wp-config.php<\/code> constant always wins over the settings screen where one applies; the screen says so when it is being overridden.<\/p><\/dd>\n<dt id=\"i%20run%20multisite.%20does%20the%20password%20policy%20apply%20per%20site%3F\"><h3>I run multisite. Does the password policy apply per site?<\/h3><\/dt>\n<dd><p>The setting is stored per site; the effect is not. WordPress keeps one user table for the whole network, so a password is checked against whichever site it is being set on \u2014 and once set, it is that person's password everywhere. Exempting a role on one subsite decides what happens when a password is changed <em>there<\/em>; it does not exempt those accounts from another site's policy. In practice the strictest site on the network sets the floor for anyone who changes their password on it.<\/p>\n\n<p>Keel can now govern it as well as document it. Under <strong>Network Admin \u2192 Settings \u2192 Keel Defaults<\/strong>, a Super Admin can decide any setting for the whole network; sites see it as locked and cannot change it. Tick the password rules there and the network has one policy instead of a floor set by whichever site is strictest.<\/p>\n\n<p>Nothing is written into your sites. A network value is applied when a setting is read, so a site's own saved settings are untouched \u2014 untick a setting later and every site returns to exactly the value it had. Settings left unticked stay each site's own business.<\/p><\/dd>\n<dt id=\"i%20already%20have%20another%20defaults%20or%20security%20plugin.%20can%20i%20run%20both%3F\"><h3>I already have another defaults or security plugin. Can I run both?<\/h3><\/dt>\n<dd><p>You can, but you probably should not, and Keel will tell you when it matters.<\/p>\n\n<p>Some settings are applied through WordPress filters that transform a value in priority order \u2014 session length is the clearest example. Another callback on the same filter does not prove a conflict: two plugins may reach the same outcome or govern different parts of a structured result.<\/p>\n\n<p>Keel reports a structural overlap only when it is registered on an authoritative policy hook and a callback attributable to another active plugin is registered there too. It never executes the other plugin's callback to diagnose the overlap. The notice appears on the Plugins screen, on <strong>Settings \u2192 Keel<\/strong>, and on the dashboard, where it can be dismissed until the overlap changes. The full detail is under <strong>Tools \u2192 Site Health<\/strong>.<\/p>\n\n<p>That evidence confirms shared ownership of a hook, not that the plugins' configured outcomes disagree. Keel asks you to compare their settings and never recommends deactivation from callback presence alone. Mail, authentication, comment-query, capability, and unattributable overlaps stay unconfirmed and informational.<\/p>\n\n<p>There is a limit worth knowing. WordPress ships tiny helper callbacks such as <code>__return_false<\/code>; the callback belongs to WordPress, not the plugin that registered it. Keel labels that limitation unconfirmed instead of guessing from source code or naming a plugin without evidence.<\/p>\n\n<p>Keel also stays out of the fight where it has nothing to say: when a setting is still at the value WordPress itself uses, Keel does not register the filter at all, so it cannot override a deliberate choice another plugin has made \u2014 and it will not report a conflict on a setting it is not itself setting.<\/p><\/dd>\n<dt id=\"why%20is%20there%20no%20password%20strength%20meter%3F\"><h3>Why is there no password strength meter?<\/h3><\/dt>\n<dd><p>WordPress ships one, but it is JavaScript: it advises the person typing and cannot refuse anything, so a password set over the REST API, WP-CLI, or a form with scripts disabled never meets it. Keel enforces length, breach screening, a blocklist and a personal-context check server-side instead, where they cannot be bypassed. See the Help tab on the settings screen.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<p>Versions before 0.5.9 were not published to the directory. The entries below are the development history that led to the first release.<\/p>\n\n<h4>0.5.9<\/h4>\n\n<ul>\n<li>Breach-cache entries can no longer be reached again after the plugin is removed and reinstalled. On a site with Redis or Memcached, WordPress keeps transients in the object cache rather than the database, where the uninstaller's queries cannot follow them. Each installation now has its own cache namespace, so anything left behind belongs to an installation that no longer exists.<\/li>\n<li>Rewrote the Help menu's Environments and Overlapping plugins tabs, and corrected how the environment type is described: WordPress reads the <code>WP_ENVIRONMENT_TYPE<\/code> constant and <code>wp_get_environment_type()<\/code> reports the result, not the other way round.<\/li>\n<\/ul>\n\n<h4>0.5.8<\/h4>\n\n<ul>\n<li>Header names, constants and file names in the Help menu are shown as code again rather than as running text. Three paragraphs across two tabs had lost the formatting the rest of the help uses.<\/li>\n<\/ul>\n\n<h4>0.5.7<\/h4>\n\n<ul>\n<li>Breach-screening reports are now based on evidence rather than the absence of it. A successful lookup clears an earlier failure, which it did not before \u2014 so a site that recovered kept being told it had a problem, against the notice's own promise. Site Health also distinguishes \"the last lookup completed\" from \"nothing has ever run here\", and notices when the lookup has been switched off with the filter rather than only the constant.<\/li>\n<li>Grouped settings that depend on another setting now show and hide as you change it. The three XML-RPC method controls were the only settings rendered inside a group, and the script that reveals dependent settings only looked at ungrouped ones \u2014 so those three stayed as they were until the page reloaded, and never announced the relationship to a screen reader.<\/li>\n<li>Network-locked sliders and role checkboxes now behave as locked. They said they were locked and then accepted edits anyway. The server always refused the value; now the screen agrees with it.<\/li>\n<li>Deleting the plugin clears breach-cache data held in a persistent object cache. On a site with an external cache the cached hash prefixes were not database rows at all, so they outlived the plugin by up to twelve hours.<\/li>\n<li>Tightened the check that removes the settings option from before the rename. It deleted any stored array carrying one of five fairly ordinary keys, which could have taken an unrelated option with it.<\/li>\n<\/ul>\n\n<h4>0.5.6<\/h4>\n\n<ul>\n<li>Breach screening now says when it is not working. If the Have I Been Pwned lookup cannot be completed \u2014 the service is unreachable, rate-limiting, or something else answered in its place \u2014 Site Health reports it instead of the check being skipped in silence. Passwords are still never blocked by an outage, and the rest of the password policy is unaffected; the difference is that a site whose screening stopped working weeks ago can now find out.<\/li>\n<li>Added help for environments and for overlapping plugins. The Help menu on the settings screen explains why email stops on staging, what the environment indicator is for, and what the two kinds of overlap report actually mean \u2014 including why some overlaps name a plugin and some cannot.<\/li>\n<\/ul>\n\n<h4>0.5.5<\/h4>\n\n<ul>\n<li>Fixed a confusing conflict report. Where a callback could not be traced back to a plugin, Site Health said \"callbacks from Unattributed callback\" \u2014 which reads as the name of a plugin, and several such lines read as several plugins, none of them the one the notice had actually named. It now says a callback could not be traced to a plugin, and the notice accounts for those settings too, so the two screens agree.<\/li>\n<\/ul>\n\n<h4>0.5.4<\/h4>\n\n<ul>\n<li>A setting that is not taking effect now says so where you will see it. Keel could already detect that something else on the site was overriding one of its settings, but it reported that only under Site Health \u2014 which nobody opens until something has already gone wrong. It now appears on the dashboard and the plugins screen, which is where the overlap warnings already were. This is the case you most need telling about, because a plugin that switches a feature off using one of WordPress's own helper functions leaves nothing to name it by.<\/li>\n<li>Fixed a comments heading appearing on posts with comments switched off. Keel reported the count as the number zero where WordPress reports it as the text \"0\", and core's Comments Title block compares the two exactly \u2014 so it did not take its early return. Affected block themes, which is the default.<\/li>\n<li>Pingbacks are now watched for the same override as comments. Both are decided by the same setting and were registered together, but only comments was checked.<\/li>\n<\/ul>\n\n<h4>0.5.3<\/h4>\n\n<ul>\n<li>Fixed the login screen's logo link. Removing, unlinking or replacing the logo is supposed to point that link at your site's home page; it pointed at your home page with <code>https:\/\/wordpress.org\/<\/code> appended, which goes nowhere. Affected every site with any of those three behaviours set.<\/li>\n<li>Keel can now see another plugin that switches the same setting off in the same way it does. WordPress stores one entry per callback, so when two plugins register the identical callback at the identical priority the second replaces the first and only one remains \u2014 which meant a plugin doing exactly what Keel does could be reported as Keel's own registration, or hide Keel's. Keel now uses callbacks that are its alone, so both are always visible and the report can tell them apart.<\/li>\n<li>Every stylesheet and script Keel adds now goes through the WordPress asset API instead of being written straight into the page. Nothing changes on screen. It means a site can dequeue, override, or defer any of it by handle, and that caching and asset-optimizing plugins can see it \u2014 none of which was possible while the markup was printed directly.<\/li>\n<li>The settings screen's CSS and JavaScript are now static files rather than markup rebuilt on every page load, so a browser caches them. The admin-menu-width slider carries its labels and widths as data attributes, which also makes its script work for any number of sliders rather than being re-emitted once per field.<\/li>\n<li>The network policy screen and the per-site settings screen now share one copy of the script that refuses changes to a locked control. There were two, and they had already drifted apart.<\/li>\n<li>Deleting the plugin now also removes the settings option from before the rename, which a site that had run both the older plugin and this one kept as an orphaned autoloaded row. Deleting from the Plugins screen left it behind, because nothing in the current code refers to that name any more.<\/li>\n<li>Dropped the compiled Canadian English translation from the plugin package. Translations for every locale are generated and delivered by translate.wordpress.org; shipping a catalog alongside that only means two sources for the same strings.<\/li>\n<\/ul>\n\n<h4>0.5.2<\/h4>\n\n<ul>\n<li>Fixed a setting being switched off by saving a different one. On WordPress 6.4 to 6.9 the AI Connectors control is not shown, because those versions have no AI connectors to turn off \u2014 but saving any other setting still read the missing checkbox as \"off\" and rewrote the stored value from on to off. Silently, and against a note in 0.4.0 saying the stored value was left alone. A site that had chosen to block connectors would have reached WordPress 7.0 with them enabled.<\/li>\n<li>A setting the screen does not show is no longer changed by saving the screen. This is the same protection settings locked by <code>wp-config.php<\/code> already had, for the same reason: the form has no business speaking for a control it did not draw.<\/li>\n<li>Network Admin no longer offers a network-wide policy for a feature the running WordPress does not have, and an existing policy for one survives a save rather than being read as switched off.<\/li>\n<li>The Network Admin role list shows role names again rather than their internal slugs, and no longer emits a PHP notice for each one.<\/li>\n<li>A plugin that only removes entries from the block inserter is no longer reported as competing with Keel. Two plugins restricting which blocks are available both get their way, so reporting a collision there suggested deactivating a plugin that works alongside this one.<\/li>\n<\/ul>\n\n<h4>0.5.1<\/h4>\n\n<ul>\n<li>Removed effect probes from policy-overlap detection. Diagnostics no longer execute another plugin's callbacks with synthetic or real user\/post context, so reporting an overlap cannot send mail, write data, terminate the request, mutate hooks, or trigger other callback side effects.<\/li>\n<li>Restored structural detection on authoritative hooks: Keel must be registered on the hook and the other callback must be attributable to an active plugin. The report confirms shared ownership only, tells administrators to compare settings, and does not recommend deactivation from presence alone.<\/li>\n<li>Memoized the overlap report for each request and added adversarial coverage for mutating, throwing, and terminating callbacks, plus guards against hook-registry mutation and overstated UI copy.<\/li>\n<\/ul>\n\n<h4>0.5.0<\/h4>\n\n<ul>\n<li>Added post-revision retention: new activations keep 10 revisions, existing sites preserve their previous unlimited behavior on upgrade, <code>-1<\/code> means unlimited, and <code>0<\/code> disables future revisions. Numeric or false <code>WP_POST_REVISIONS<\/code> policy locks both site and network controls.<\/li>\n<li>Author feeds now return an explicit 404 when author archives are disabled. They were already closed by the archive's broad 301 because WordPress sets both query flags; the corrected test now proves that routing fact against the real request.<\/li>\n<li>Rebuilt overlapping-policy detection around confirmed, compatible, and unconfirmed effects. Callback presence alone no longer generates deactivation advice, and the incorrect claim that mail\/comment-query callbacks stop after the first non-null value is gone.<\/li>\n<\/ul>\n\n<h4>0.4.1<\/h4>\n\n<ul>\n<li>Removed the unconfirmed half of the overlapping-settings check. It reported a plugin when something untraceable was registered on a setting Keel also sets and that plugin's source mentioned the same filter \u2014 but WordPress itself, and Keel itself, both register through the same untraceable helper functions, so the first of those two conditions was true on nearly every setting. That left one weak signal doing the work of two, and it named plugins that were not doing anything: Clearfy and WP Master Toolkit were both reported on five settings between them while registering nothing at all. Confirmed detection is unchanged and unaffected.<\/li>\n<li>The check now says what it cannot see, on the settings screen and in Site Health. A plugin that turns something off by handing one of WordPress's own helper functions to a filter cannot be traced back from that filter, so a clear result means nothing traceable was found rather than nothing competing.<\/li>\n<\/ul>\n\n<h4>0.4.0<\/h4>\n\n<ul>\n<li>The plugin folder is now <code>keel-defaults<\/code> rather than <code>keel<\/code>, and the text domain moved with it. WordPress.org serves translations as <code>{slug}-{locale}.mo<\/code>, so a text domain that is not the slug means no translation ever loads \u2014 silently, with nothing to search for.<\/li>\n<li>Keel now tells you when another plugin is setting the same things it is. Session length, comment behaviour, the editor and a dozen other settings are applied through WordPress filters that return a single value: when more than one plugin uses the same filter, only one of them takes effect, there is no error, and the ones that lost go on showing the values they set. The check names the plugins and the settings \u2014 on the Plugins screen, on Keel's own screen, and in full under Site Health.<\/li>\n<li>What it cannot see is stated where it is reported. A plugin that turns a feature off by handing one of WordPress's own helper functions to a filter leaves nothing to trace back to it, so a clear result means nothing traceable was found rather than nothing competing.<\/li>\n<li>A conflict needs Keel to be on the hook too. Turning a default off takes Keel out of the contest and the report follows, instead of warning about a setting Keel has stopped touching.<\/li>\n<li>Capability conflicts are judged by the capability rather than by the filter. Nearly every plugin that adds a custom role uses the same filter Keel uses to take <code>unfiltered_html<\/code> away, and almost none of them touch that capability; only the ones that do are reported.<\/li>\n<li>AI Connectors no longer appears on WordPress versions that have no AI connectors. The setting is gated on the core function rather than a version number, and its stored value is left alone, so a site that upgrades to 7.0 finds the default already there.<\/li>\n<li>Tested against WordPress 7.1, behaviourally rather than by reading the release notes.<\/li>\n<\/ul>\n\n<h4>0.3.0<\/h4>\n\n<ul>\n<li>Multisite: a Super Admin can decide any setting for the whole network, under Network Admin \u2192 Settings \u2192 Keel Defaults. Sites see those settings as locked. Policy applies when a value is read rather than being written into each site, so a site's own saved settings are untouched and lifting the policy returns every site to exactly what it had.<\/li>\n<li>A locked setting now stays locked when the form is saved, not only when it is drawn. A wp-config constant or a network policy was enforced in the rendered control and nowhere else, so a submission could still write the value it protected. It never took effect, but the stored setting drifted from what the screen showed.<\/li>\n<li>Locked controls can be reached by keyboard and screen reader, and say why they are locked. They were disabled, which removes them from the tab order \u2014 so the explanation attached to them was announced on a focus that never happened.<\/li>\n<li>Settings that hide when another choice makes them irrelevant now tell assistive technology which control governs them, and whether they are showing.<\/li>\n<li>The staging environment indicator failed WCAG AA contrast at 2.41:1 against the 4.5:1 minimum for text that size. Every environment colour is now checked by the test suite.<\/li>\n<li>The admin menu width slider announces its setting as a word rather than a position, and no longer repeats itself on every keypress.<\/li>\n<li>Site Health \u2192 Info groups the defaults by category instead of repeating the group name on every row, and the section is named \"Keel Defaults\" rather than \"Keel\".<\/li>\n<li>Number settings report their unit, so Site Health says \"14 days\" rather than \"14\".<\/li>\n<li>X-Frame-Options is left alone inside the Customizer preview, which sets that header itself so the preview can load.<\/li>\n<li>Translation catalogs rebuilt: 68 strings in the code were missing from the template, and the en_CA catalog translated nothing because every string it named had been reworded.<\/li>\n<li>An XML-RPC help tab covering the whole family \u2014 what it is, why four switches rather than one, the Jetpack constraint, and why system.multicall's reputation is out of date.<\/li>\n<li>A \"try it live\" Playground link that follows each stable release.<\/li>\n<\/ul>\n\n<h4>0.2.0<\/h4>\n\n<ul>\n<li>First stable release. The initial feature set was frozen; what changed since the scaffold is listed below.<\/li>\n<li>Comment teardown now reaches past the rendered page: comment queries are answered empty, comment blocks stop rendering in block themes, comment feeds return a real 404 instead of a redirect loop, and the comment count reports zero.<\/li>\n<li>A closed REST API stops advertising itself \u2014 the <code>&lt;link rel&gt;<\/code>, the <code>Link:<\/code> header and the RSD entry all go \u2014 and oEmbed stays reachable through the gate so other sites embedding yours do not silently degrade to a bare link.<\/li>\n<li>Author identity no longer leaks past a hidden author archive. oEmbed responses drop <code>author_name<\/code> and <code>author_url<\/code>, and the users sitemap provider is removed.<\/li>\n<li>Uninstall leaves nothing behind: settings, the last-login user meta and the breach-screening transients are all removed, network-wide on multisite.<\/li>\n<li>Activation seeds every existing site on a network, and a subsite created afterwards is seeded too, so a later schema change cannot move some sites and not others.<\/li>\n<li>Site Health reports every default and its state under Info, flags only what warrants attention under Status, and names other active plugins setting the same defaults.<\/li>\n<li>Outgoing mail is suppressed outside production, and the settings screen says so on screen rather than only in a notice.<\/li>\n<li>The session-length filter stands down when it has nothing to say, so it does not overrule a host or another plugin that has already decided.<\/li>\n<li>Environment detection no longer overrides a site that declares <code>WP_ENVIRONMENT_TYPE<\/code> through an environment variable rather than the constant.<\/li>\n<\/ul>\n\n<h4>0.1.0-dev<\/h4>\n\n<ul>\n<li>Removed the reserved-usernames default. It refused to create accounts named <code>admin<\/code>, <code>support<\/code>, <code>info<\/code> and 70 others, which is a reasonable policy for a managed fleet and a presumptuous one for a general-purpose defaults plugin \u2014 the list is long, opinionated, and includes names an ordinary site legitimately uses (<code>manager<\/code>, <code>marketing<\/code>, <code>sales<\/code>, <code>office<\/code>, <code>client<\/code>). Existing accounts were never affected and still are not. A stored setting is ignored and drops out of the option on the next save; no migration is needed. To keep the behaviour, WordPress's own filter does it in one call: <code>add_filter( 'illegal_user_logins', function ( $logins ) { return array_merge( $logins, array( 'admin', 'administrator', 'root' ) ); } );<\/code><\/li>\n<li>Initial scaffold: base imported from Better by Default (WPYEG, GPL-3.0-or-later) and re-identified as Keel. Work in progress.<\/li>\n<li>Licence is now GPL-2.0-or-later, matching WordPress core and the upstream 10up Experience code some defaults descend from. Relicensed by the sole author of the carried-over work; nothing is withdrawn, since \"or later\" still permits GPL-3 terms.<\/li>\n<li>Breach screening can be switched off with the KEEL_DISABLE_HIBP constant or the keel_disable_hibp filter, and a truncated or malformed range response is now rejected instead of parsed and cached.<\/li>\n<\/ul>","raw_excerpt":"39 sane WordPress defaults, each one a switch you can see and turn off \u2014 security, updates, privacy, UX, and performance.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/359065","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=359065"}],"author":[{"embeddable":true,"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/dpknauss"}],"wp:attachment":[{"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=359065"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=359065"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=359065"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=359065"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=359065"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/mfe.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=359065"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}