{"id":329552,"date":"2026-06-22T20:09:50","date_gmt":"2026-06-22T20:09:50","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/gallop\/"},"modified":"2026-10-14T20:31:39","modified_gmt":"2026-10-14T20:31:39","slug":"gallop","status":"publish","type":"plugin","link":"https:\/\/twd.wordpress.org\/plugins\/gallop\/","author":23502399,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.4.0","stable_tag":"1.4.0","tested":"7.1.3","requires":"6.4","requires_php":"8.1","requires_plugins":null,"header_name":"Gallop","header_author":"Gallop Software","header_description":"A purpose-built REST API for Next.js websites \u2014 fetch a page's post, SEO, and site data in one request, with built-in cookie login support for authenticated front ends.","assets_banners_color":"665c5c","last_updated":"2026-10-14 20:31:39","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/gallop.software\/headless-wordpress","header_author_uri":"https:\/\/gallop.software","rating":0,"author_block_rating":0,"active_installs":0,"downloads":314,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.1.1":{"tag":"0.1.1","author":"gallopsoftware","date":"2026-06-22 20:09:37","revision":3582339},"0.2.0":{"tag":"0.2.0","author":"gallopsoftware","date":"2026-09-28 15:04:13","revision":3717439},"1.0.0":{"tag":"1.0.0","author":"gallopsoftware","date":"2026-09-28 21:05:42","revision":3717919},"1.4.0":{"tag":"1.4.0","author":"gallopsoftware","date":"2026-10-14 20:31:39","revision":3747103}},"upgrade_notice":{"1.4.0":"<p>Adds a route for front ends that confirm a new member&#039;s address themselves. Nothing else changes.<\/p>","1.3.2":"<p>Fixes logging in with a password that contains a quote or a backslash.<\/p>","1.3.1":"<p>Removes the mailing routes added in 1.3.0. Nothing else changes.<\/p>","1.2.0":"<p>Adds member accounts for front ends: login, sign-up with email confirmation, password reset, and profiles. Nothing changes for existing requests, and the new permission starts switched off.<\/p>","1.1.0":"<p>Adds comment endpoints and an API key for front ends that write to WordPress. Nothing changes for existing requests. No key exists until you generate one, and its permissions start switched off.<\/p>","1.0.0":"<p>First stable release. No functional changes from 0.2.0.<\/p>","0.2.0":"<p>Adds collection and index endpoints, id and slug lookups, and richer post payloads. Existing requests and fields are unchanged, but rendered block content no longer carries the stray <code>and<\/code> tags earlier versions added. Check any front-end styling that relied on them.<\/p>","0.1.0":"<p>Initial release.<\/p>"},"ratings":[],"assets_icons":{"icon-256x256.png":{"filename":"icon-256x256.png","revision":3582339,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3582339,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3582339,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.1.1","0.2.0","1.0.0","1.4.0"],"block_files":[],"assets_screenshots":{"screenshot-1.jpg":{"filename":"screenshot-1.jpg","revision":3582339,"resolution":"1","location":"assets","locale":"","width":2600,"height":917},"screenshot-2.jpg":{"filename":"screenshot-2.jpg","revision":3582339,"resolution":"2","location":"assets","locale":"","width":3456,"height":1992},"screenshot-3.jpg":{"filename":"screenshot-3.jpg","revision":3582339,"resolution":"3","location":"assets","locale":"","width":3456,"height":1988},"screenshot-4.jpg":{"filename":"screenshot-4.jpg","revision":3582339,"resolution":"4","location":"assets","locale":"","width":3456,"height":1990}},"screenshots":{"1":"The Gallop REST API in action \u2014 a request to the <code>gallop\/v1<\/code> namespace returning post, SEO, and site data.","2":"Login UI: a reader signing in on the front end with their WordPress account.","3":"Settings tab: point Gallop at your Next.js production URL, configure proxy IP trust for auth rate limiting, and generate the API key your front end writes with.","4":"Post Types tab: register REST-enabled custom post types (no code) and view their slugs and REST endpoints."}},"plugin_section":[],"plugin_tags":[710,181897,141196,211409,23853],"plugin_category":[38],"plugin_contributors":[268407],"plugin_business_model":[],"class_list":["post-329552","plugin","type-plugin","status-publish","hentry","plugin_tags-authentication","plugin_tags-decoupled","plugin_tags-headless","plugin_tags-nextjs","plugin_tags-rest-api","plugin_category-authentication","plugin_contributors-gallopsoftware","plugin_committers-gallopsoftware"],"banners":{"banner":"https:\/\/ps.w.org\/gallop\/assets\/banner-772x250.png?rev=3582339","banner_2x":"https:\/\/ps.w.org\/gallop\/assets\/banner-1544x500.png?rev=3582339","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/gallop\/assets\/icon-256x256.png?rev=3582339","icon_2x":"https:\/\/ps.w.org\/gallop\/assets\/icon-256x256.png?rev=3582339","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/gallop\/assets\/screenshot-1.jpg?rev=3582339","caption":"The Gallop REST API in action \u2014 a request to the <code>gallop\/v1<\/code> namespace returning post, SEO, and site data."},{"src":"https:\/\/ps.w.org\/gallop\/assets\/screenshot-2.jpg?rev=3582339","caption":"Login UI: a reader signing in on the front end with their WordPress account."},{"src":"https:\/\/ps.w.org\/gallop\/assets\/screenshot-3.jpg?rev=3582339","caption":"Settings tab: point Gallop at your Next.js production URL, configure proxy IP trust for auth rate limiting, and generate the API key your front end writes with."},{"src":"https:\/\/ps.w.org\/gallop\/assets\/screenshot-4.jpg?rev=3582339","caption":"Post Types tab: register REST-enabled custom post types (no code) and view their slugs and REST endpoints."}],"raw_content":"<!--section=description-->\n<p>Gallop is the headless WordPress REST API for Next.js that's FAST and SIMPLE. Keep the WordPress you write in, lose the theme that slows you down \u2014 and ship your whole page from one request.<\/p>\n\n<blockquote>\n  <p>The WordPress editing experience your team already knows, with the speed of a fully decoupled Next.js front end. One endpoint, one response, done.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/gallop.software\/headless-wordpress\">Visit the Gallop homepage &amp; documentation \u2192<\/a><\/p>\n\n<p>https:\/\/www.youtube.com\/watch?v=G-xBcAS8ux4<\/p>\n\n<p>Most headless setups make you stitch together a waterfall of core WordPress REST calls per page \u2014 <code>\/wp\/v2\/posts<\/code>, <code>\/wp\/v2\/media<\/code>, meta, and taxonomy \u2014 then bolt on a JWT layer and an auth service just to log a user in. Gallop replaces all of that.<\/p>\n\n<p><strong>One request. The whole page.<\/strong> Hand Gallop a URI and it returns the post body, an SEO block, and your global site data \u2014 already joined, already resolved, ready to render. <strong>The API is the point:<\/strong> a dedicated, Next.js-shaped REST namespace (<code>\/wp-json\/gallop\/v1<\/code>) so your front-end code stays simple \u2014 one fetch, one response, ready to render.<\/p>\n\n<h4>Why choose Gallop?<\/h4>\n\n<ul>\n<li><strong>One round trip instead of five.<\/strong> Everything a page needs \u2014 <code>post<\/code>, <code>seo<\/code>, and <code>site<\/code> \u2014 in a single response.<\/li>\n<li><strong>No JWT, no separate auth service.<\/strong> Members log in, sign up, and reset passwords against WordPress's own user table, through your front end's server, and reading content needs no key at all.<\/li>\n<li><strong>Comments that stay in WordPress.<\/strong> Visitors comment on your front end; WordPress stores, moderates, and notifies exactly as it does for its own comment form.<\/li>\n<li><strong>SEO done for you.<\/strong> With Yoast active, the <code>seo<\/code> block ships search-ready out of the box.<\/li>\n<li><strong>No-code custom post types.<\/strong> Register REST-enabled CPTs from the admin \u2014 no <code>register_post_type()<\/code> boilerplate.<\/li>\n<li><strong>Instant publishing.<\/strong> Publish in WordPress and Gallop revalidates the affected Next.js routes automatically \u2014 no full redeploy.<\/li>\n<li><strong>Framework-agnostic JSON.<\/strong> Next.js is the reference target, but any HTTP client can consume the API.<\/li>\n<li><strong>Keep your workflow.<\/strong> Your editors keep the exact WordPress publishing experience they already rely on.<\/li>\n<\/ul>\n\n<h4>Everything a page needs, in one request<\/h4>\n\n<p>Hand Gallop a URI and it returns the whole page in a single response: the full <code>post<\/code>, its <code>seo<\/code> metadata, and your global <code>site<\/code> data, already joined, already resolved, ready to render. <code>GET|POST \/gallop\/v1\/post<\/code> handles posts and pages, and <code>POST \/gallop\/v1\/category<\/code> does the same for taxonomy archives. No waterfall of <code>\/wp\/v2\/posts<\/code>, <code>\/wp\/v2\/media<\/code>, meta, and taxonomy calls per page \u2014 one round trip instead of five, with no JWT, API keys, or complicated authentication to set up. Your front end stays simple, and your pages load fast.<\/p>\n\n<p>The SEO is done for you. With Yoast active, the <code>seo<\/code> block is populated straight from Yoast's indexables (canonical, meta description, OpenGraph, robots flags, reading time) so every page ships search-ready out of the box. Without Yoast, <code>seo<\/code> comes back as an empty object instead of disappearing, so your front end can check it and fall back to its own defaults.<\/p>\n\n<h4>Login, members, and comments, already wired up<\/h4>\n\n<p>https:\/\/vimeo.com\/1200535505<\/p>\n\n<p>Moving off a WordPress template usually means rebuilding everything it gave you for free. Gallop ships with it already done. Readers log in, sign up, reset a password, and edit their profile on your Next.js site, and every one of them is an ordinary WordPress user. Editors sign in with their normal WordPress credentials. There is no JWT layer and no separate auth service to stand up: your front end's server talks to WordPress with one key, and keeps its own session for each member.<\/p>\n\n<p>Comments stay in WordPress too. Visitors and members comment on your front end; WordPress stores, moderates, and notifies exactly as it does for its own comment form.<\/p>\n\n<h4>Settings and custom post types, configured from WordPress<\/h4>\n\n<p>https:\/\/vimeo.com\/1196416170<\/p>\n\n<p>Point Gallop at your Next.js production URL and it 301-redirects public WordPress front-end requests to the matching path on your headless host. Admin, REST API, and previews are left untouched.<\/p>\n\n<p>Register REST-enabled custom post types from the Post Types tab and they're immediately available through the Gallop namespace \u2014 no <code>register_post_type()<\/code> boilerplate, no developer round trip. Core post types are left alone, and content you create survives a deactivate\/uninstall.<\/p>\n\n<h4>Trusted on real production sites<\/h4>\n\n<p>Gallop isn't a proof of concept \u2014 it powers live production sites today:<\/p>\n\n<ul>\n<li><strong>douglasnewby.com<\/strong><\/li>\n<li><strong>cmwelectric.com<\/strong><\/li>\n<li><strong>winx.gallop.software<\/strong><\/li>\n<\/ul>\n\n<p>Every one edits in WordPress and ships a fast Next.js front end \u2014 headless content, real Google rankings and structured data, no theme holding it back, and the same WordPress publishing experience your team already knows.<\/p>\n\n<p><a href=\"https:\/\/gallop.software\/headless-wordpress\">See how Gallop powers headless WordPress \u2192<\/a><\/p>\n\n<h4>REST endpoints<\/h4>\n\n<p>All endpoints live under the <code>gallop\/v1<\/code> namespace.<\/p>\n\n<ul>\n<li><code>GET|POST \/gallop\/v1\/post<\/code> \u2014 Resolve a post and return <code>post<\/code>, <code>seo<\/code>, and <code>site<\/code> payloads. Accepts <code>uri<\/code>, <code>id<\/code>, or <code>slug<\/code> with <code>type<\/code>.<\/li>\n<li><code>GET|POST \/gallop\/v1\/posts<\/code> \u2014 A filtered, paged collection of posts of one <code>type<\/code>. Accepts <code>category<\/code>, <code>include<\/code>, <code>meta_key<\/code> \/ <code>meta_value<\/code> \/ <code>meta_compare<\/code>, <code>orderby<\/code>, <code>order<\/code>, <code>size<\/code>, <code>offset<\/code>, and <code>fields<\/code>.<\/li>\n<li><code>GET|POST \/gallop\/v1\/posts\/list<\/code> \u2014 Slug, uri, and modified date for every published post of a <code>type<\/code>, for sitemaps and static builds. Optional <code>parent<\/code>.<\/li>\n<li><code>POST \/gallop\/v1\/category<\/code> \u2014 Resolve a category URI to a term and return <code>category<\/code>, <code>seo<\/code>, and <code>site<\/code> payloads.<\/li>\n<li><code>GET  \/gallop\/v1\/comments<\/code> \u2014 The approved comments on a <code>post<\/code>, oldest first, as a flat list with <code>parent<\/code> ids. Public.<\/li>\n<li><code>POST \/gallop\/v1\/comments<\/code> \u2014 Submit a visitor's or a member's comment. Requires the API key.<\/li>\n<li><code>POST \/gallop\/v1\/members\/login<\/code>, <code>POST \/gallop\/v1\/members<\/code>, <code>POST \/gallop\/v1\/members\/confirm<\/code>, <code>POST \/gallop\/v1\/members\/create<\/code>, <code>POST \/gallop\/v1\/members\/reset-request<\/code>, <code>POST \/gallop\/v1\/members\/reset<\/code>, <code>GET|PATCH \/gallop\/v1\/members\/{id}<\/code> \u2014 Members: log in, sign up, confirm an address, create an account the front end confirmed, reset a password, read and edit a profile. All require the API key.<\/li>\n<li><code>POST \/gallop\/v1\/auth\/login<\/code>, <code>POST \/gallop\/v1\/auth\/logout<\/code>, <code>GET \/gallop\/v1\/auth\/session<\/code> \u2014 The older cookie-based login, kept for front ends on the same registered domain that use it.<\/li>\n<\/ul>\n\n<h4>Members<\/h4>\n\n<p>Readers get accounts, and WordPress is the only place they live. Every route takes the API key with the <strong>Manage members<\/strong> permission and is called by your front end's <strong>server<\/strong>, which keeps its own session for the member; WordPress is asked only when someone logs in or changes something.<\/p>\n\n<ul>\n<li><strong>Log in<\/strong> with an email address or username and a password, checked by <code>wp_authenticate()<\/code> as the visitor, so login-protection plugins see the visitor's address. No WordPress cookies are set.<\/li>\n<li><strong>Sign up<\/strong>: nothing is created until the reader opens the confirmation email; then they become a Subscriber, verified, and logged in, with a one-time key so the front end can offer them a password at once. An address that already has an account is only marked subscribed. An email is never a way past a password.<\/li>\n<li><strong>Reset a password<\/strong> with WordPress's own reset keys, emailed as a link to your front end.<\/li>\n<li><strong>Profiles<\/strong>: name, email, password, and two email choices. Changing the email or password needs the current one and ends every session the front end holds, through a <code>sessionVersion<\/code> handed back with each request.<\/li>\n<li><strong>Comments as themselves<\/strong>, recorded as theirs, and accepted where only registered users may comment.<\/li>\n<\/ul>\n\n<p>A password set through Gallop, by <code>\/members\/create<\/code>, <code>\/members\/reset<\/code>, or a profile change, must be 8 to 128 characters. Logging in accepts whatever password the account already has.<\/p>\n\n<p>Five wrong passwords for one login name from one address within fifteen minutes return HTTP 429; each address is sent at most one email every ten minutes and five a day; login, sign-up and reset answer the same whether or not the address is known.<\/p>\n\n<h4>Comments<\/h4>\n\n<p>Show WordPress comments on a headless front end, and let visitors leave them, without giving up anything WordPress does with a comment.<\/p>\n\n<ul>\n<li><code>GET \/gallop\/v1\/comments?post=&lt;id&gt;<\/code> returns the post's approved <code>comments<\/code>, oldest first, each with <code>id<\/code>, <code>parent<\/code>, <code>authorName<\/code>, <code>authorUrl<\/code>, <code>isPostAuthor<\/code>, <code>avatar<\/code>, <code>dateGmt<\/code>, and rendered <code>content<\/code>. Build the thread from <code>parent<\/code>. It also returns <code>open<\/code>, <code>count<\/code>, <code>requireNameEmail<\/code>, <code>threadDepth<\/code>, and <code>truncated<\/code>. A commenter's email address, IP address, and browser are never returned.<\/li>\n<li><code>POST \/gallop\/v1\/comments<\/code> takes <code>post<\/code>, <code>parent<\/code>, <code>authorName<\/code>, <code>authorEmail<\/code>, <code>authorUrl<\/code>, <code>content<\/code>, and the visitor's <code>ip<\/code>, <code>userAgent<\/code>, and <code>referer<\/code>. It answers <code>201<\/code> with the <code>comment<\/code>, its <code>status<\/code> (<code>approved<\/code>, <code>hold<\/code>, <code>spam<\/code>, or <code>trash<\/code>), and the post's new <code>count<\/code>.<\/li>\n<\/ul>\n\n<p>A submitted comment is handed to <code>wp_handle_comment_submission()<\/code>, the function WordPress's own comment form uses. Your Discussion settings, moderation, the duplicate and flood checks, anti-spam plugins such as Akismet, and notification emails all behave as they do for a comment left on a WordPress site. Core's own REST route for comments sends no notification emails.<\/p>\n\n<p>Send the request from your front end's <strong>server<\/strong>, never from the browser: it carries the API key.<\/p>\n\n<h4>API key<\/h4>\n\n<p>Reading from Gallop needs no key. Writing does: a request that changes something in WordPress on a visitor's behalf has to prove it comes from your front end's own server.<\/p>\n\n<ul>\n<li><strong>Generate it<\/strong> under Gallop \u2192 Settings \u2192 Front-end connection. It is shown once. Gallop stores only a hash of it.<\/li>\n<li><strong>Send it<\/strong> in the <code>X-Gallop-WP-Key<\/code> header, over HTTPS.<\/li>\n<li><strong>Keep it on your server<\/strong>, in a secret environment variable such as <code>GALLOP_WP_API_KEY<\/code>. Never give it a <code>NEXT_PUBLIC_<\/code> prefix, and never put it in code.<\/li>\n<li><strong>Or set it in <code>wp-config.php<\/code><\/strong> with <code>define( 'GALLOP_WP_API_KEY', 'gallopwp_\u2026' );<\/code>. A key set there takes priority over a generated one.<\/li>\n<li><strong>Permissions are off by default.<\/strong> A key can do only what you tick: \"Submit comments\" and \"Manage members\". A permission added by a later version is never switched on for you.<\/li>\n<li><strong>Regenerating<\/strong> replaces the key at once.<\/li>\n<\/ul>\n\n<p>Gallop removes the key from the request as soon as it has been checked, so other plugins that read or forward request headers never see it.<\/p>\n\n<h4>SEO integration<\/h4>\n\n<p>When the <a href=\"https:\/\/wordpress.org\/plugins\/wordpress-seo\/\">Yoast SEO<\/a> plugin is active, the <code>seo<\/code> block in the post and category responses is populated from Yoast's indexable data (canonical, meta description, OpenGraph fields, robots flags, reading time, etc.). Without Yoast, <code>seo<\/code> is returned as an empty object so clients can branch safely.<\/p>\n\n<h4>Data stored<\/h4>\n\n<ul>\n<li><code>gallop_post_types<\/code> (option) \u2014 your custom post type definitions.<\/li>\n<li><code>gallop_nextjs_production_url<\/code> (option) \u2014 the redirect target, if configured.<\/li>\n<li><code>gallop_trust_forwarded_ip<\/code> (option) \u2014 whether to trust reverse-proxy IP headers when rate-limiting auth (default off).<\/li>\n<li><code>gallop_api_key_hash<\/code> (option) \u2014 a hash of your API key, with the date it was generated and its last four characters. Never the key itself.<\/li>\n<li><code>gallop_api_key_permissions<\/code> (option) \u2014 what the key is allowed to do.<\/li>\n<li><code>gallop_verified<\/code>, <code>gallop_subscribed<\/code>, <code>gallop_reply_emails<\/code>, <code>gallop_session_version<\/code> (user meta) \u2014 whether a member's address is waiting for confirmation, their email choices, and their session version. <code>_gallop_reply_notified<\/code> (comment meta) \u2014 a reply already emailed about.<\/li>\n<li><code>gallop_auth_*<\/code> (transients) \u2014 short-lived login rate-limit counters.<\/li>\n<li><code>gallop_pending_*<\/code> (transients) \u2014 sign-ups awaiting confirmation, up to 48 hours: address, names, and a hash of the link's key.<\/li>\n<li><code>gallop_confirm_sent_*<\/code> (transients) \u2014 when each address was last emailed, for a day.<\/li>\n<li><code>gallop_key_fail_*<\/code> (transients) \u2014 short-lived counters of wrong API keys, by address.<\/li>\n<li><code>gallop_key_reveal_*<\/code> (transient) \u2014 a newly generated key, held for up to two minutes so it can be shown once after the page reloads, then deleted.<\/li>\n<\/ul>\n\n<p>Comments submitted through Gallop are ordinary WordPress comments, and members are ordinary WordPress users, stored where WordPress stores them.<\/p>\n\n<h3>Privacy<\/h3>\n\n<p>Gallop does not send any data to external services. All data stays on your WordPress site.<\/p>\n\n<p>The login endpoints authenticate users with WordPress's built-in functions. To mitigate brute-force attacks, Gallop temporarily stores failed-login counters in WordPress transients keyed by a hash of the login name and the visitor's IP address. These counters expire automatically (typically within 15 minutes) and are removed on plugin uninstall.<\/p>\n\n<p>A sign-up is kept as a transient for up to 48 hours (the address, the names typed, and a hash of the confirmation key), then becomes a WordPress user or expires. Gallop emails members through <code>wp_mail()<\/code> only what they asked for: confirmation and reset links, a note that an address already has an account, and replies to their comments if they turned that on. It records when each address was last emailed, for a day, to limit how often one can be written to.<\/p>\n\n<p>When a request presents a wrong API key, Gallop counts it in a transient keyed by a hash of the requesting IP address. That counter also expires within 15 minutes and is removed on uninstall.<\/p>\n\n<p>A comment submitted through <code>\/gallop\/v1\/comments<\/code> is stored by WordPress with the commenter's name, email address, IP address, and browser, as WordPress stores every comment. Those details are supplied by your front end, and the IP address and browser are presented to other active plugins while the comment is saved, as they would be for a comment left on the site itself: an anti-spam plugin may send them to its own service, under its own privacy terms. Comments are returned by <code>\/gallop\/v1\/comments<\/code> without the email address, IP address, or browser. If avatars are switched on, each comment includes an avatar address from your avatar service (Gravatar by default), which contains a hash of the commenter's email address, as WordPress's own themes and REST API expose.<\/p>\n\n<p>No personal data is shared with third parties. No tracking, analytics, or telemetry is performed.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>gallop<\/code> folder to <code>\/wp-content\/plugins\/<\/code>, or install the ZIP from the Plugins screen.<\/li>\n<li>Activate <strong>Gallop<\/strong> from the Plugins screen.<\/li>\n<li>Point your Next.js front end at <code>https:\/\/your-wp-site.example\/wp-json\/gallop\/v1<\/code> and start fetching the <code>post<\/code> and <code>category<\/code> endpoints.<\/li>\n<li>(Optional) Open <strong>Gallop<\/strong> in the admin menu to register custom post types and set your Next.js production URL.<\/li>\n<li>(Optional) To accept comments or members from your front end, generate an API key under <strong>Front-end connection<\/strong>, tick <strong>Submit comments<\/strong> and <strong>Manage members<\/strong>, and give the key to your front end's server. Member emails link to the Next.js production URL, so set it first.<\/li>\n<\/ol>\n\n<p>Requires PHP 8.1 or higher. The plugin will refuse to boot and show an admin notice on older PHP versions.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"do%20i%20have%20to%20use%20next.js%3F\"><h3>Do I have to use Next.js?<\/h3><\/dt>\n<dd><p>No. Gallop's REST endpoints are framework-agnostic JSON. Next.js is the reference target and the redirect feature is named for it, but any HTTP client can consume the API.<\/p><\/dd>\n<dt id=\"does%20the%20redirect%20break%20the%20wordpress%20admin%20or%20previews%3F\"><h3>Does the redirect break the WordPress admin or previews?<\/h3><\/dt>\n<dd><p>No. The redirect runs on <code>template_redirect<\/code> only, skips any request with a <code>preview=true<\/code> or <code>_wp*<\/code> query parameter, and never touches <code>\/wp-admin<\/code> or <code>\/wp-json<\/code>. Leave the Next.js URL setting blank to disable redirection entirely.<\/p><\/dd>\n<dt id=\"how%20do%20members%20log%20in%20on%20the%20front%20end%3F\"><h3>How do members log in on the front end?<\/h3><\/dt>\n<dd><p>The front end's server sends the login name, password, and the visitor's address to <code>\/gallop\/v1\/members\/login<\/code> with the API key. Gallop checks the password with <code>wp_authenticate()<\/code>, the function behind wp-login.php, and answers with the member's details; the front end then keeps a session of its own for them. No WordPress cookies are involved, so the front end can be on any domain. A two-factor plugin on the WordPress login screen does not apply to front-end logins. The older <code>\/gallop\/v1\/auth\/*<\/code> routes, which set WordPress's own cookies for a front end on the same registered domain, still work.<\/p><\/dd>\n<dt id=\"what%20happens%20when%20someone%20signs%20up%3F\"><h3>What happens when someone signs up?<\/h3><\/dt>\n<dd><p>Nothing is written to the users table. Gallop keeps the address and names for up to 48 hours and emails a confirmation link to the front end. When the link is opened the front end calls <code>\/gallop\/v1\/members\/confirm<\/code>, and only then is a user created, with the Subscriber role. If the address already had an account, it is marked verified (and subscribed, if that was asked) and the person is told to log in; the link never logs anyone into an existing account. WordPress's \"Anyone can register\" setting is not consulted: the Manage members permission is the switch.<\/p><\/dd>\n<dt id=\"can%20the%20front%20end%20send%20the%20confirmation%20email%20itself%3F\"><h3>Can the front end send the confirmation email itself?<\/h3><\/dt>\n<dd><p>Yes. A front end that confirms the address its own way skips the two routes above and calls <code>POST \/gallop\/v1\/members\/create<\/code> with <code>email<\/code>, <code>password<\/code>, and optionally <code>firstName<\/code>, <code>lastName<\/code> and <code>subscribe<\/code>, once the address is confirmed. Gallop creates the user at once, with the Subscriber role, marked verified, with that password, and answers 201 with the member. It sends no email and keeps nothing waiting. An address that already has an account is answered 409 <code>gallop_member_exists<\/code> and left as it is. Gallop takes the front end's word that the address was confirmed: only call this after it was.<\/p><\/dd>\n<dt id=\"what%20can%20a%20members%20request%20be%20refused%20with%3F\"><h3>What can a members request be refused with?<\/h3><\/dt>\n<dd><p>Every refusal has a code. Where WordPress gave a reason of its own, it is in <code>data.reason<\/code>.<\/p>\n\n<ul>\n<li>The key: <code>gallop_key_missing<\/code> (401), <code>gallop_key_invalid<\/code> (401), <code>gallop_key_forbidden<\/code> (403, the key lacks Manage members), <code>gallop_https_required<\/code> (403), <code>gallop_key_rate_limited<\/code> (429).<\/li>\n<li>Logging in: <code>gallop_member_invalid_credentials<\/code> (401, the same whether the account is missing or the password is wrong), <code>gallop_member_rate_limited<\/code> (429).<\/li>\n<li>The request: <code>gallop_member_invalid_ip<\/code> (400, login, sign-up and reset requests need the visitor's <code>ip<\/code>), <code>gallop_member_invalid_email<\/code>, <code>gallop_member_invalid_name<\/code>, <code>gallop_member_weak_password<\/code> (all 400).<\/li>\n<li>A link: <code>gallop_member_link_invalid<\/code> (400, the confirmation or reset link has expired or was already used).<\/li>\n<li>A session: <code>gallop_member_session_invalid<\/code> (401, the member does not exist or <code>sessionVersion<\/code> is out of date; ask them to log in again). Commenting as a member can be refused with it too.<\/li>\n<li>A profile change: <code>gallop_member_wrong_password<\/code> (403, the current password is needed to change the email or password and was not right), <code>gallop_member_email_taken<\/code> (409), <code>gallop_member_update_failed<\/code> (400).<\/li>\n<li>Creating an account: <code>gallop_member_exists<\/code> (409), <code>gallop_member_create_failed<\/code> (500).<\/li>\n<li>The site: <code>gallop_member_front_end_missing<\/code> (500, the Next.js Production URL is not set, so no email can link to the front end).<\/li>\n<\/ul><\/dd>\n<dt id=\"what%20emails%20does%20gallop%20send%3F\"><h3>What emails does Gallop send?<\/h3><\/dt>\n<dd><p>Four, all through <code>wp_mail()<\/code> and all to the member: a confirmation link, a note that an address already has an account, a password reset link, and, if the member asked for it, a message when someone replies to their comment. Every link points at your Next.js production URL. The <code>gallop_member_email<\/code> filter can change or suppress any of them.<\/p><\/dd>\n<dt id=\"is%20the%20login%20endpoint%20rate-limited%3F\"><h3>Is the login endpoint rate-limited?<\/h3><\/dt>\n<dd><p>Yes. Five failed attempts per login name + client IP within fifteen minutes return HTTP 429 until the window expires. Successful logins clear the counter. Login, sign-up, and reset requests also answer the same whether or not the account exists, and take the same time, so they cannot be used to find out who has an account.<\/p><\/dd>\n<dt id=\"what%20is%20the%20%22trust%20proxy%20ip%20headers%22%20setting%3F\"><h3>What is the \"Trust proxy IP headers\" setting?<\/h3><\/dt>\n<dd><p>By default Gallop uses <code>REMOTE_ADDR<\/code> for the per-IP portion of the login rate limit. If your site sits behind a trusted reverse proxy (Cloudflare, a load balancer, etc.) that overwrites the client-IP headers, enable <strong>Trust proxy IP headers<\/strong> on the Gallop settings screen so <code>CF-Connecting-IP<\/code> \/ <code>X-Forwarded-For<\/code> are used instead. Leave it off on direct-served sites \u2014 turning it on without a trusted proxy lets attackers spoof those headers to bypass the rate limit. The setting can also be overridden in code via the <code>gallop_trust_forwarded_ip<\/code> filter.<\/p><\/dd>\n<dt id=\"which%20posts%20can%20comments%20be%20read%20from%3F\"><h3>Which posts can comments be read from?<\/h3><\/dt>\n<dd><p>Published posts of a public type that are not password protected. Anything else, a missing post included, answers the same <code>404 gallop_comments_post_not_found<\/code>, so the route cannot be used to find out that a draft exists. The newest 500 comments are returned by default; <code>truncated<\/code> is <code>true<\/code> when there are more, and <code>gallop_comments_max<\/code> raises the ceiling.<\/p><\/dd>\n<dt id=\"what%20can%20submitting%20a%20comment%20be%20refused%20with%3F\"><h3>What can submitting a comment be refused with?<\/h3><\/dt>\n<dd><p>Every refusal has a code, and WordPress's own reason in <code>data.reason<\/code>.<\/p>\n\n<ul>\n<li>The key: <code>gallop_key_missing<\/code> (401), <code>gallop_key_invalid<\/code> (401), <code>gallop_key_forbidden<\/code> (403, the key lacks the permission), <code>gallop_https_required<\/code> (403), <code>gallop_key_rate_limited<\/code> (429).<\/li>\n<li>The site does not accept it: <code>gallop_comment_closed<\/code> (403), <code>gallop_comment_login_required<\/code> (403), <code>gallop_comments_post_not_found<\/code> (404).<\/li>\n<li>The request: <code>gallop_comment_name_email_required<\/code>, <code>gallop_comment_invalid_email<\/code>, <code>gallop_comment_empty<\/code>, <code>gallop_comment_too_long<\/code>, <code>gallop_comment_name_too_long<\/code>, <code>gallop_comment_email_too_long<\/code>, <code>gallop_comment_url_too_long<\/code>, <code>gallop_comment_invalid_ip<\/code> (all 400).<\/li>\n<li>The reply: <code>gallop_comment_invalid_parent<\/code>, <code>gallop_comment_thread_too_deep<\/code>, <code>gallop_comment_threading_disabled<\/code> (all 400). A parent must be an approved comment on the same post.<\/li>\n<li>WordPress's checks: <code>gallop_comment_duplicate<\/code> (409), <code>gallop_comment_flood<\/code> (429).<\/li>\n<li>Anything else: <code>gallop_comment_rejected<\/code> (400, another plugin refused it), <code>gallop_comment_save_failed<\/code> (500).<\/li>\n<\/ul><\/dd>\n<dt id=\"how%20does%20wordpress%20see%20the%20visitor%20rather%20than%20my%20server%3F\"><h3>How does WordPress see the visitor rather than my server?<\/h3><\/dt>\n<dd><p>Your server passes on the visitor's IP address, browser, and the page they commented from. While the comment is being saved, Gallop presents those details to WordPress and to other plugins in place of the server's own, then puts the originals back. That is what lets WordPress record, throttle, and spam-check each comment against the person who wrote it. The details are only accepted from a request whose key has been verified.<\/p><\/dd>\n<dt id=\"the%20key%20is%20refused%20with%20gallop_https_required.%20why%3F\"><h3>The key is refused with gallop_https_required. Why?<\/h3><\/dt>\n<dd><p>The key is only accepted over HTTPS, or in a <code>local<\/code> environment. If your site is served over HTTPS and you still see this, it is probably behind a proxy that ends TLS without telling WordPress. Fixing <code>is_ssl()<\/code> in <code>wp-config.php<\/code> is the right answer, since WordPress's own Application Passwords need it too. The <code>gallop_api_key_transport_secure<\/code> filter is there if you cannot.<\/p><\/dd>\n<dt id=\"what%20happens%20after%20too%20many%20wrong%20keys%3F\"><h3>What happens after too many wrong keys?<\/h3><\/dt>\n<dd><p>After ten wrong keys from one address in fifteen minutes, further wrong keys from it are answered <code>429 gallop_key_rate_limited<\/code>. A correct key is never refused for that reason, so nobody can lock your front end out by sending wrong ones. If the key in <code>wp-config.php<\/code> is not valid, every request that needs a key is refused and an admin notice says so.<\/p><\/dd>\n<dt id=\"why%20do%20comments%20need%20an%20api%20key%20when%20reading%20does%20not%3F\"><h3>Why do comments need an API key when reading does not?<\/h3><\/dt>\n<dd><p>Anyone may read a published post's comments, as on any WordPress site. Submitting one is different: it arrives from your front end's server on a visitor's behalf, carrying the visitor's IP address for WordPress to record and check. The key is how Gallop knows the request, and that address, really came from your server. Without it, anyone could post straight to WordPress, skip whatever spam checks your front end runs, and claim any address they liked.<\/p><\/dd>\n<dt id=\"i%20lost%20my%20api%20key.%20can%20i%20see%20it%20again%3F\"><h3>I lost my API key. Can I see it again?<\/h3><\/dt>\n<dd><p>No. Gallop stores a hash of the key, not the key. Regenerate it under Gallop \u2192 Settings \u2192 Front-end connection and install the new one. The old key stops working as soon as you do.<\/p><\/dd>\n<dt id=\"are%20comments%20held%20for%20moderation%3F\"><h3>Are comments held for moderation?<\/h3><\/dt>\n<dd><p>That is decided by your Discussion settings, exactly as for a comment left on a WordPress site. The response to a submitted comment reports its <code>status<\/code>, so your front end can tell a visitor that their comment is waiting for approval.<\/p><\/dd>\n<dt id=\"do%20i%20need%20yoast%20seo%3F\"><h3>Do I need Yoast SEO?<\/h3><\/dt>\n<dd><p>No. If Yoast is not active the <code>seo<\/code> field in responses is an empty object. With Yoast active, Gallop reads from its indexables to populate canonical, OpenGraph, and robots data.<\/p><\/dd>\n<dt id=\"does%20gallop%20modify%20core%20post%20types%3F\"><h3>Does Gallop modify core post types?<\/h3><\/dt>\n<dd><p>No. Posts, pages, media, and built-in taxonomies are left alone. Only post types you create through the Gallop admin screen are registered by this plugin.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20i%20deactivate%20or%20delete%20the%20plugin%3F\"><h3>What happens if I deactivate or delete the plugin?<\/h3><\/dt>\n<dd><p>Deactivating stops Gallop from registering its post types and REST routes; content created under those post types stays in the database. Deleting the plugin (via the Plugins screen) additionally removes every <code>gallop_*<\/code> option and transient. Posts authored under your custom post types are intentionally left in place so they survive an uninstall\/reinstall. So are comments and member accounts; what Gallop recorded about members (a few user meta rows) is removed.<\/p><\/dd>\n<dt id=\"which%20actions%20and%20filters%20does%20gallop%20provide%3F\"><h3>Which actions and filters does Gallop provide?<\/h3><\/dt>\n<dd><p><strong>Actions<\/strong><\/p>\n\n<ul>\n<li><code>gallop_auth_login_success<\/code> \u2014 fires after a successful REST login. Args: <code>WP_User $user<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_auth_login_failed<\/code> \u2014 fires after a failed REST login. Args: <code>string $username<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_auth_logout<\/code> \u2014 fires after a REST logout. Args: <code>WP_User $user<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_comment_submitted<\/code> \u2014 fires after a submitted comment has been saved, whatever its status. Args: <code>WP_Comment $comment<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_comment_rejected<\/code> \u2014 fires when WordPress refused a submitted comment. Args: <code>WP_Error $error<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_api_key_generated<\/code> \u2014 fires after a key has been generated. The key is not passed. Args: <code>string $keyId<\/code>.<\/li>\n<li><code>gallop_api_key_verified<\/code> \u2014 fires when a request presented a valid key for a permission it holds. Args: <code>string $keyId<\/code>, <code>string $capability<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_api_key_failed<\/code> \u2014 fires when a request presented a key that is not this site's. Args: <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_member_login<\/code> \u2014 fires after a member logged in through the front end. Args: <code>WP_User $user<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_member_login_failed<\/code> \u2014 fires after a failed member login. Args: <code>string $login<\/code>, <code>WP_Error $error<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_member_signup_requested<\/code> \u2014 fires when someone asked to sign up. The address is not passed: it is not yet known to belong to them. Args: <code>string $ip<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_member_verified<\/code> \u2014 fires when a member's address is confirmed, or an account is created already confirmed. Args: <code>WP_User $user<\/code>.<\/li>\n<li><code>gallop_member_registered<\/code> \u2014 fires after an account was created through the front end, by <code>\/members\/confirm<\/code> or <code>\/members\/create<\/code>. Args: <code>WP_User $user<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<li><code>gallop_member_password_reset<\/code> \u2014 fires after a member reset their password. Args: <code>WP_User $user<\/code>.<\/li>\n<li><code>gallop_member_updated<\/code> \u2014 fires after a member changed their profile. Args: <code>WP_User $user<\/code>, <code>WP_REST_Request $request<\/code>.<\/li>\n<\/ul>\n\n<p><strong>Filters<\/strong><\/p>\n\n<ul>\n<li><code>gallop_trust_forwarded_ip<\/code> \u2014 filter the boolean controlling whether reverse-proxy IP headers (<code>CF-Connecting-IP<\/code>, <code>X-Forwarded-For<\/code>) are trusted when rate-limiting REST auth. Defaults to the \"Trust proxy IP headers\" setting. Only enable behind a trusted proxy that overwrites these headers, otherwise the per-IP rate limit can be bypassed by spoofing them.<\/li>\n<li><code>gallop_resolved_post<\/code> \u2014 the post a <code>\/gallop\/v1\/post<\/code> request resolved to, before its payload is built. Return another <code>WP_Post<\/code> to serve that one instead.<\/li>\n<li><code>gallop_pre_post_data<\/code> \u2014 return an array to replace the <code>post<\/code> payload entirely; <code>gallop_post_data<\/code> still runs on it. <code>gallop_post_data<\/code>, <code>gallop_seo_data<\/code>, <code>gallop_site_data<\/code> \u2014 filter the <code>post<\/code>, <code>seo<\/code> and <code>site<\/code> payloads. Each is passed the <code>WP_Post<\/code>.<\/li>\n<li><code>gallop_category_data<\/code>, <code>gallop_category_seo_data<\/code>, <code>gallop_category_site_data<\/code> \u2014 the same for <code>\/gallop\/v1\/category<\/code>. Each is passed the <code>WP_Term<\/code>.<\/li>\n<li><code>gallop_post_type_queryable<\/code> \u2014 whether a post type can be read by id, slug, or listing. Default: public types registered with <code>show_in_rest<\/code>.<\/li>\n<li><code>gallop_meta_key_queryable<\/code> \u2014 whether <code>\/gallop\/v1\/posts<\/code> may filter on a meta key. Default: keys registered for the type with <code>show_in_rest<\/code>. <code>gallop_meta_comparisons<\/code> \u2014 the accepted <code>meta_compare<\/code> values.<\/li>\n<li><code>gallop_posts_page_max<\/code> \u2014 the most posts one <code>\/gallop\/v1\/posts<\/code> request returns. Default no ceiling. <code>gallop_posts_list_max<\/code> \u2014 the most <code>\/gallop\/v1\/posts\/list<\/code> returns. Default 5,000.<\/li>\n<li><code>gallop_member_data<\/code> \u2014 filter a member as returned to the front end. Args: <code>array $data<\/code>, <code>WP_User $user<\/code>.<\/li>\n<li><code>gallop_member_email<\/code> \u2014 filter an email before it is sent to a member, or return <code>false<\/code> to send nothing. Args: <code>array $email<\/code> (<code>to<\/code>, <code>subject<\/code>, <code>message<\/code>, <code>headers<\/code>), <code>string $kind<\/code> (<code>confirm<\/code>, <code>exists<\/code>, <code>reset<\/code> or <code>reply<\/code>), <code>array $context<\/code>.<\/li>\n<li><code>gallop_comment_data<\/code> \u2014 filter one comment as it is returned. <code>gallop_comments_data<\/code> \u2014 filter the whole response to a request for a post's comments.<\/li>\n<li><code>gallop_comments_max<\/code> \u2014 the most comments one request returns. Default 500.<\/li>\n<li><code>gallop_comment_avatar_sizes<\/code> \u2014 the avatar sizes returned with each comment, in pixels. Default 48 and 96.<\/li>\n<li><code>gallop_comments_cache_control<\/code> \u2014 the <code>Cache-Control<\/code> header sent with a post's comments.<\/li>\n<li><code>gallop_api_key_capabilities<\/code> \u2014 the permissions a key can be granted. Adding one makes it available to tick; it grants it to nothing.<\/li>\n<li><code>gallop_api_key_max_failed_attempts<\/code> \u2014 how many wrong keys one address may present in fifteen minutes. Default 10.<\/li>\n<li><code>gallop_api_key_transport_secure<\/code> \u2014 whether a request arrived over a connection safe to carry the key.<\/li>\n<\/ul><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.4.0<\/h4>\n\n<ul>\n<li>Added <code>POST \/gallop\/v1\/members\/create<\/code>, for a front end that sends the confirmation email itself: it creates a verified member with the password they chose, and sends no email. Needs the API key with the Manage members permission. An address that already has an account is answered 409 <code>gallop_member_exists<\/code>.<\/li>\n<li>Sign-up through <code>POST \/gallop\/v1\/members<\/code> and <code>\/members\/confirm<\/code> works as before.<\/li>\n<\/ul>\n\n<h4>1.3.2<\/h4>\n\n<ul>\n<li>Fixed: a password containing a quote, a double quote or a backslash, set in WordPress, was refused by <code>\/gallop\/v1\/members\/login<\/code> and <code>\/gallop\/v1\/auth\/login<\/code>. Passwords are now checked and stored the way WordPress's own login screen does it. A password already set through the reset or profile routes keeps working.<\/li>\n<\/ul>\n\n<h4>1.3.1<\/h4>\n\n<ul>\n<li>Removes what 1.3.0 added: <code>GET \/gallop\/v1\/members<\/code>, <code>POST \/gallop\/v1\/members\/{id}\/subscription<\/code>, the \"Send mailings\" permission, and the <code>email<\/code> field in the confirm response. A front end that sends mailings keeps its own list of subscribers.<\/li>\n<li>Everything else is as in 1.2.0.<\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<ul>\n<li>Added a list of users and an unsubscribe route for front ends that send mailings. Withdrawn in 1.3.1.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>Added members: <code>POST \/gallop\/v1\/members\/login<\/code>, <code>POST \/gallop\/v1\/members<\/code> (sign up), <code>\/members\/confirm<\/code>, <code>\/members\/reset-request<\/code>, <code>\/members\/reset<\/code>, and <code>GET|PATCH \/members\/{id}<\/code>. A front end's server can let readers log in with <code>wp_authenticate()<\/code>, sign up with email confirmation, reset a password with WordPress's own keys, and edit a profile, with no WordPress cookies involved. All need the API key with the new \"Manage members\" permission, which starts off.<\/li>\n<li>A member logged in on the front end can comment as themselves: <code>POST \/gallop\/v1\/comments<\/code> takes <code>user<\/code> and <code>sessionVersion<\/code>, and <code>GET<\/code> reports <code>openToMembers<\/code>.<\/li>\n<li>A member can ask to be emailed when someone replies to their comment.<\/li>\n<li>Accounts with the Subscriber role that have never chosen count as subscribed to new posts by email; a member's own choice always wins.<\/li>\n<li>New filters: <code>gallop_member_data<\/code>, <code>gallop_member_email<\/code>. New actions: <code>gallop_member_login<\/code>, <code>gallop_member_login_failed<\/code>, <code>gallop_member_signup_requested<\/code>, <code>gallop_member_verified<\/code>, <code>gallop_member_registered<\/code>, <code>gallop_member_password_reset<\/code>, <code>gallop_member_updated<\/code>.<\/li>\n<li>Existing endpoints and their responses are unchanged, except that <code>GET \/gallop\/v1\/comments<\/code> gains one field.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Added <code>\/gallop\/v1\/comments<\/code>. <code>GET<\/code> returns a post's approved comments as a flat list with <code>parent<\/code> ids, ready to thread. <code>POST<\/code> submits a visitor's comment through WordPress's own comment form handling, so Discussion settings, moderation, the duplicate and flood checks, anti-spam plugins, and notification emails all behave as they do for a comment left on a WordPress site.<\/li>\n<li>Added an API key for front ends that write to WordPress. Generate it under Gallop \u2192 Settings \u2192 Front-end connection, or define <code>GALLOP_WP_API_KEY<\/code> in <code>wp-config.php<\/code>. Only a hash is stored. It is sent in the <code>X-Gallop-WP-Key<\/code> header, over HTTPS only.<\/li>\n<li>What a key may do is switched on one permission at a time, and every permission starts off. \"Submit comments\" is the first.<\/li>\n<li>New filters: <code>gallop_comment_data<\/code>, <code>gallop_comments_data<\/code>, <code>gallop_comments_max<\/code>, <code>gallop_comment_avatar_sizes<\/code>, <code>gallop_comments_cache_control<\/code>, <code>gallop_api_key_capabilities<\/code>, <code>gallop_api_key_max_failed_attempts<\/code>, <code>gallop_api_key_transport_secure<\/code>. New actions: <code>gallop_comment_submitted<\/code>, <code>gallop_comment_rejected<\/code>, <code>gallop_api_key_generated<\/code>, <code>gallop_api_key_verified<\/code>, <code>gallop_api_key_failed<\/code>.<\/li>\n<li>Existing endpoints and their responses are unchanged.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>First stable release. No functional changes from 0.2.0: the REST API and its response shapes are now considered stable.<\/li>\n<\/ul>\n\n<h4>0.2.0<\/h4>\n\n<ul>\n<li>Added <code>\/gallop\/v1\/posts<\/code> for filtered collections: post type, category, id list, meta filtering, ordering, paging, and field selection. Defaults to 10 per page; a site can set a ceiling with <code>gallop_posts_page_max<\/code>. The response reports the <code>size<\/code> and <code>offset<\/code> actually used.<\/li>\n<li>Added <code>\/gallop\/v1\/posts\/list<\/code>, a lightweight index of every published post of a type, for sitemaps and static builds. Capped at 5,000 by default (<code>gallop_posts_list_max<\/code>) and reports <code>truncated<\/code> when the cap is reached.<\/li>\n<li><code>\/gallop\/v1\/post<\/code> can now resolve by <code>id<\/code>, or by <code>slug<\/code> together with <code>type<\/code>, as well as by <code>uri<\/code>. <code>uri<\/code> lookups behave exactly as before.<\/li>\n<li>The <code>post<\/code> payload gains <code>uri<\/code>, <code>link<\/code>, <code>excerpt<\/code>, <code>featuredImage<\/code>, <code>author<\/code>, and <code>categories<\/code>. Existing fields are unchanged.<\/li>\n<li><code>\/gallop\/v1\/auth\/login<\/code> and <code>\/auth\/session<\/code> now also return a <code>wp_rest<\/code> nonce, so a browser session can make cookie-authenticated REST requests.<\/li>\n<li>New filters to extend payloads without forking: <code>gallop_pre_post_data<\/code>, <code>gallop_post_data<\/code>, <code>gallop_seo_data<\/code>, <code>gallop_site_data<\/code>, <code>gallop_category_data<\/code>, <code>gallop_category_seo_data<\/code>, <code>gallop_category_site_data<\/code>, <code>gallop_resolved_post<\/code>.<\/li>\n<li>Only public post types registered with <code>show_in_rest<\/code> can be read by id, slug, or listing, and only meta keys registered for that type with <code>show_in_rest<\/code> can be filtered on. This matches core REST behaviour. Widen either with <code>gallop_post_type_queryable<\/code> or <code>gallop_meta_key_queryable<\/code>. Accepted meta comparisons can be changed with <code>gallop_meta_comparisons<\/code>.<\/li>\n<li>Post content now renders with the post set as the global post, so shortcodes and dynamic blocks that read it see the right one. Block content is no longer run through <code>do_blocks()<\/code> twice, which had let <code>wpautop<\/code> add stray <code>&lt;p&gt;<\/code> and <code>&lt;br&gt;<\/code> tags.<\/li>\n<\/ul>\n\n<h4>0.1.1<\/h4>\n\n<ul>\n<li>Documentation: expanded the plugin description.<\/li>\n<\/ul>\n\n<h4>0.1.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<li>Admin UI for registering REST-enabled custom post types.<\/li>\n<li><code>\/gallop\/v1\/post<\/code> and <code>\/gallop\/v1\/category<\/code> endpoints with optional Yoast SEO payloads.<\/li>\n<li>Cookie-based REST auth endpoints (<code>\/auth\/login<\/code>, <code>\/auth\/logout<\/code>, <code>\/auth\/session<\/code>) with per-username\/IP rate limiting.<\/li>\n<li>Optional Next.js production URL redirect for public front-end requests.<\/li>\n<\/ul>","raw_excerpt":"A REST API for headless Next.js sites: a page's post, SEO, and site data in one request, plus comments and member accounts that stay in WordPress.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/329552","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=329552"}],"author":[{"embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/gallopsoftware"}],"wp:attachment":[{"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=329552"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=329552"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=329552"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=329552"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=329552"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/twd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=329552"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}