{"id":375359,"date":"2026-10-29T18:53:01","date_gmt":"2026-10-29T18:53:01","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/contentrain-bridge\/"},"modified":"2026-10-29T19:29:27","modified_gmt":"2026-10-29T19:29:27","slug":"contentrain-bridge","status":"publish","type":"plugin","link":"https:\/\/hau.wordpress.org\/plugins\/contentrain-bridge\/","author":23573584,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"0.8.5","stable_tag":"0.8.5","tested":"7.1.4","requires":"6.2","requires_php":"7.4","requires_plugins":null,"header_name":"Contentrain Bridge \u2013 Export to Markdown, JSON & Git","header_author":"Contentrain","header_description":"Model and export WordPress content and interface text as Contentrain JSON\/Markdown, with optional GitHub delivery.","assets_banners_color":"fafafa","last_updated":"2026-10-29 19:29:27","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/Contentrain\/wordpress-bridge","header_author_uri":"https:\/\/contentrain.io\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":79,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.8.4":{"tag":"0.8.4","author":"abb65","date":"2026-10-29 18:52:39","revision":3773797},"0.8.5":{"tag":"0.8.5","author":"abb65","date":"2026-10-29 19:29:27","revision":3773849}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3773797,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3773797,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3774812,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3774812,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.8.4","0.8.5"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3773797,"resolution":"1","location":"assets","locale":"","width":1316,"height":911},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3773797,"resolution":"2","location":"assets","locale":"","width":1358,"height":896},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3773797,"resolution":"3","location":"assets","locale":"","width":1316,"height":911},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3773797,"resolution":"4","location":"assets","locale":"","width":1290,"height":892}},"screenshots":{"1":"Step 1: choose the content types to export, whether drafts and comments travel, and where interface text is read from.","2":"Step 2: review the content models and the interface text that travels with them.","3":"Step 3: download a ZIP or deliver the export to a new branch in your own GitHub repository.","4":"The coverage report: every place WordPress keeps content, counted and split into what was exported, what was left out and why."}},"plugin_section":[],"plugin_tags":[185980,1859,141196,4608,250566],"plugin_category":[59],"plugin_contributors":[289569],"plugin_business_model":[],"class_list":["post-375359","plugin","type-plugin","status-publish","hentry","plugin_tags-astro","plugin_tags-export","plugin_tags-headless","plugin_tags-markdown","plugin_tags-static-site","plugin_category-utilities-and-tools","plugin_contributors-abb65","plugin_committers-abb65"],"banners":{"banner":"https:\/\/ps.w.org\/contentrain-bridge\/assets\/banner-772x250.png?rev=3774812","banner_2x":"https:\/\/ps.w.org\/contentrain-bridge\/assets\/banner-1544x500.png?rev=3774812","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/contentrain-bridge\/assets\/icon-128x128.png?rev=3773797","icon_2x":"https:\/\/ps.w.org\/contentrain-bridge\/assets\/icon-256x256.png?rev=3773797","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/contentrain-bridge\/assets\/screenshot-1.png?rev=3773797","caption":"Step 1: choose the content types to export, whether drafts and comments travel, and where interface text is read from."},{"src":"https:\/\/ps.w.org\/contentrain-bridge\/assets\/screenshot-2.png?rev=3773797","caption":"Step 2: review the content models and the interface text that travels with them."},{"src":"https:\/\/ps.w.org\/contentrain-bridge\/assets\/screenshot-3.png?rev=3773797","caption":"Step 3: download a ZIP or deliver the export to a new branch in your own GitHub repository."},{"src":"https:\/\/ps.w.org\/contentrain-bridge\/assets\/screenshot-4.png?rev=3773797","caption":"The coverage report: every place WordPress keeps content, counted and split into what was exported, what was left out and why."}],"raw_content":"<!--section=description-->\n<p>Contentrain Bridge turns your WordPress content into editable JSON and Markdown under Tools &gt; Contentrain Bridge. You choose the content, review how it is modelled and which interface text travels, then download a ZIP or send the result to a branch in your own GitHub repository. Both are free.<\/p>\n\n<ul>\n<li>Advanced Custom Fields groups and repeaters become their own models with their own named fields, not one anonymous bag of values.<\/li>\n<li>Free software under GPL-2.0-or-later. No account, subscription or paid service is required for either delivery.<\/li>\n<li>Your WordPress site is not modified. The plugin reads content and source files; it never patches a theme or plugin. The one exception is opt-in: a switch, off by default, that adds a group for Contentrain Migrate to the robots.txt WordPress serves (see External services).<\/li>\n<li>Every export step is started by a logged-in administrator through a nonce-protected request, or, over the REST export API, by a program that administrator authorized with an application password or a connection key (see External services).<\/li>\n<li>Exports run in bounded steps and can be resumed, so large sites do not depend on one long request.<\/li>\n<li>Comment archiving is off by default. When enabled, email addresses, IP addresses and comment metadata are excluded.<\/li>\n<li>Metadata that looks like a password, token, key, credential or personal identifier is never exported, including inside selected fields.<\/li>\n<li>Exported site data remains the site owner's data and is not placed under the plugin's GPL license merely by being exported.<\/li>\n<\/ul>\n\n<p>Media travels with the content: uploads are copied into <code>media\/<\/code> and the content is relinked to them, so the delivered repository does not depend on this site for images. A file over 8 MB, or one missing from the uploads directory, keeps its WordPress URL and is named in the coverage report. Dynamic WordPress and plugin output is not rendered during a source scan. These limits are written into the coverage report rather than implied to be complete.<\/p>\n\n<p>Want a working Astro website instead of files? Contentrain Migrate, a separate paid service, connects to this plugin with a key you create and builds a complete Astro site from your content: https:\/\/migrate.contentrain.io\/?utm_source=wordpress-org&amp;utm_medium=readme<\/p>\n\n<h4>External services<\/h4>\n\n<p>The plugin contacts no host other than this site unless you choose GitHub delivery. Two options read this site's own pages over HTTP, the way a visitor would, and send nothing elsewhere: \"Also read the text your pages render\" (the home page, a post, a page, search and not-found) and, with it, the connected-services check (the home page's script hosts).<\/p>\n\n<p><strong>GitHub<\/strong> \u2014 used only when you enter a repository and a token in step 3 and start the delivery. The plugin then calls the GitHub REST API at api.github.com to read the target repository's default branch (or the base branch you name) and file list, upload the exported files, and create one new branch holding them. What is sent: the exported content files you reviewed, a commit message, and the token you supplied. Nothing is sent to Contentrain or to any other host, and no analytics or telemetry is collected. The token is used for those requests only and is never stored in the database or in the export. Delivery never writes to your default branch and never overwrites a file you edited in Git. GitHub's terms: https:\/\/docs.github.com\/site-policy\/github-terms\/github-terms-of-service \u2014 privacy policy: https:\/\/docs.github.com\/site-policy\/privacy-policies\/github-privacy-statement<\/p>\n\n<p><strong>REST export API (inbound)<\/strong> \u2014 the plugin adds routes under <code>\/wp-json\/contentrain-bridge\/v1\/<\/code> that let a program you authorize start an export and read its files. The plugin sends nothing by itself: the program asks, with an application password of an administrator who has the export and manage_options capabilities, or with a Contentrain Migrate connection key (below). Contentrain Migrate uses these routes when you give it such a password or key to migrate this site; what Migrate then does with the content is governed by Contentrain's terms (https:\/\/contentrain.io\/terms-of-service) and privacy policy (https:\/\/contentrain.io\/privacy-policy). The program that starts an export chooses its scope: Migrate asks for private content (drafts, pending, scheduled, private and password-protected posts, never the password) and for comments, and the exported files carry each post's own status. These routes only read: no remote call creates, edits or deletes posts, settings, files, users, plugins or themes; the plugin writes only its own temporary export files outside the web root and its own bookkeeping options. Revoke the application password in your profile (or the connection key on the Bridge screen) to end that access; exports expire after a day.<\/p>\n\n<p><strong>robots.txt (only when you turn it on)<\/strong> \u2014 Tools &gt; Contentrain Bridge has a switch, off by default, \"Let Contentrain Migrate read my pages and media\". It changes only the robots.txt WordPress itself serves, through WordPress's <code>robots_txt<\/code> filter, and contacts no host. When you turn it on, robots.txt gains one group for the <code>ContentrainMigrate<\/code> crawler: the rules your robots.txt gives every crawler (<code>User-agent: *<\/code>), without the lines that close the whole site (a Disallow matching <code>\/<\/code>), followed by <code>Allow<\/code> lines for the REST API (<code>\/wp-json\/<\/code>), your uploaded media (<code>\/wp-content\/uploads\/<\/code>) and <code>\/<\/code>. By robots.txt's longest-match rule those Allow lines open only those paths: a rule such as <code>Disallow: \/wp-<\/code> still keeps wp-admin closed. Paths you keep closed, such as wp-admin, stay closed to it, and every other crawler sees robots.txt exactly as before. Turning it off removes the group, and uninstalling the plugin removes the setting. If your site serves a robots.txt file from disk, WordPress cannot change it: the switch stays off and the screen shows the lines to add yourself.<\/p>\n\n<h3>Privacy<\/h3>\n\n<p>The plugin stores export ownership and a source revision marker. It sends nothing anywhere on its own; an export leaves the site only when you download it, deliver it to GitHub, or authorize a program to read it through the REST export API with an administrator's application password or a Contentrain Migrate connection key. For a connection key the plugin stores a fingerprint of the key (never the key), the administrator who created it, the Migrate order it is paired with, the list of exports it started, and the time and IP address of its last use; creating a new key or revoking it, and uninstalling the plugin, removes them. A request made with the key runs with no WordPress user. Export files are written to a private, per-site directory outside the web root that only the server can read, are removed on uninstall, and expire after a day. An export can contain site content, authors' display names and WordPress usernames, selected post metadata and, if you or the program you authorized chose it, privacy-minimized comments of every status (approved, pending, spam and trash) and draft, pending, scheduled, private and password-protected posts (never the password). A program authorized through the REST API chooses the scope; Contentrain Migrate asks for private content. Treat a downloaded export as you would a WordPress export file. If you deliver to GitHub, the exported content is sent to GitHub under the account whose token you provide; choose a private repository when the export includes draft or private content.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>In Plugins &gt; Add New, search for \"Contentrain Bridge\" and install it. You can also download <code>contentrain-bridge.zip<\/code> from the latest GitHub release (https:\/\/github.com\/Contentrain\/wordpress-bridge\/releases\/latest), the same archive, and install it with Plugins &gt; Add New &gt; Upload Plugin.<\/li>\n<li>Activate Contentrain Bridge in WordPress.<\/li>\n<li>Open Tools &gt; Contentrain Bridge.<\/li>\n<li>Step 1: choose content types and options, then prepare the content.<\/li>\n<li>Step 2: review the generated models and the interface-text candidates, then validate and finalize.<\/li>\n<li>Step 3: download the ZIP, or deliver to a branch in your own GitHub repository.<\/li>\n<\/ol>\n\n<p>Updates arrive through WordPress like any other plugin's, from the WordPress.org directory. A ZIP installed from a GitHub release is updated by uploading the newer release ZIP the same way; WordPress replaces the installed version. Export snapshots are temporary and are not affected.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"is%20contentrain%20migrate%20or%20studio%20required%3F\"><h3>Is Contentrain Migrate or Studio required?<\/h3><\/dt>\n<dd><p>No. Modelling, validation, the ZIP download and GitHub delivery are all local and free. Migrate is an optional later step if you want an Astro website.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20send%20telemetry%3F\"><h3>Does the plugin send telemetry?<\/h3><\/dt>\n<dd><p>No. It collects no analytics and contacts no Contentrain server. The only request to another host is to the GitHub API, when you start a GitHub delivery yourself.<\/p><\/dd>\n<dt id=\"are%20media%20files%20exported%3F\"><h3>Are media files exported?<\/h3><\/dt>\n<dd><p>Yes, when you include the Media type. Each upload and its generated sizes are copied into <code>media\/<\/code> in the export, and content that pointed at a WordPress upload URL is rewritten to that path, so images keep working after this site is gone. Media records also keep the original URL. Files larger than 8 MB, files missing from disk, and anything past the export's total media budget keep their WordPress URL instead of being half-copied; the coverage report lists them. The 8 MB ceiling exists because GitHub delivery has to hold a file in memory to upload it.<\/p><\/dd>\n<dt id=\"what%20is%20the%20contentrain%20migrate%20connection%20key%3F\"><h3>What is the Contentrain Migrate connection key?<\/h3><\/dt>\n<dd><p>A way for Contentrain Migrate to read this site's content when your host blocks application passwords (some hosts strip the Authorization header they travel in). Create it under Tools &gt; Contentrain Bridge and paste it into Migrate.<\/p>\n\n<p>What it allows: Migrate can ask this site to prepare a content export and download it \u2014 published posts, pages and custom post types with their fields and metadata (secret-looking keys are always left out); if Migrate asks for it, draft, pending, scheduled, private and password-protected content too (never the password itself); media records (the files only if requested); categories, tags and menus; comments without email addresses or IP addresses; SEO, redirect and permalink settings; theme design settings; the names and versions of active plugins and the theme; and the number of published entries per content type.<\/p>\n\n<p>What it cannot do: it runs with no WordPress user and cannot change anything on this site \u2014 no post, setting, file, user, plugin or theme is written. It does not open the WordPress REST API, wp-admin or application passwords; it works only on the plugin's own export routes. The key is valid only while the administrator who created it still has the export and manage_options capabilities.<\/p>\n\n<p>Revoking: revoke it on the same screen at any time; creating a new key retires the old one, and revoking removes the exports it prepared. It is shown once and only a fingerprint of it is stored. Paste it into Migrate within an hour; once Migrate has used it, it works for that one move until 14 days after it was created, until Migrate closes it when the move is done, or until you revoke it or create a new one. It can only be created and used over HTTPS. Nothing is sent from your site to Contentrain when you create it.<\/p><\/dd>\n<dt id=\"why%20would%20i%20let%20contentrain%20migrate%20read%20my%20pages%20and%20media%20in%20robots.txt%3F\"><h3>Why would I let Contentrain Migrate read my pages and media in robots.txt?<\/h3><\/dt>\n<dd><p>Contentrain Migrate respects robots.txt. If yours closes the site to crawlers (for example with \"Discourage search engines from indexing this site\"), Migrate cannot read your pages and media to move them. The switch opens the site to the ContentrainMigrate crawler only, keeps what you close for every crawler closed to it as well (it drops only the lines that close the whole site, and opens the REST API and your uploaded media), and changes nothing for other crawlers. It is off until you turn it on, and you can turn it off at any time.<\/p>\n\n<p>If Cloudflare (or another CDN) serves your robots.txt, for example Cloudflare's managed robots.txt, WordPress's robots.txt never reaches visitors and the switch cannot change it: add the lines the screen shows in that service's settings instead.<\/p><\/dd>\n<dt id=\"which%20comment%20data%20is%20exported%3F\"><h3>Which comment data is exported?<\/h3><\/dt>\n<dd><p>Only when explicitly selected: author name, URL, content, status, dates and relationships. Comments of every status are included (approved, pending, spam and trash), each with its status, so you can leave out the ones you do not want to keep. Email addresses, IP addresses, user-agent values and comment metadata are excluded.<\/p><\/dd>\n<dt id=\"what%20does%20the%20source%20scan%20read%3F\"><h3>What does the source scan read?<\/h3><\/dt>\n<dd><p>The active theme and child theme, and optionally the source files of active plugins. It reads PHP, HTML and JavaScript text to find interface strings, never executes them, skips vendor and dependency directories, and stops at 10,000 files. Every string it finds is a candidate you review before it becomes content.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>0.8.5<\/h4>\n\n<ul>\n<li>The plugin is named \"Contentrain Bridge \u2013 Export to Markdown, JSON &amp; Git\" in the plugin list and the directory.<\/li>\n<li>The Migrate card states that Contentrain Migrate is a separate paid service that reads the site through a connection key, and its link is tagged <code>utm_source=bridge<\/code> so visits from the plugin can be counted. The link sends no site data.<\/li>\n<\/ul>\n\n<h4>0.8.4<\/h4>\n\n<ul>\n<li>Widgets are read through WordPress's own widget registry: each registered widget reads only its own settings. The export no longer lists option names from the database to find widgets, so a widget whose plugin is inactive is not counted or scanned, as WordPress does not show it either.<\/li>\n<li>Connected services are found by setting names only. A setting whose name marks a credential (key, token, secret) counts as set when it is not empty; the database answers that, and the value is never read. Evidence names the setting (\"mc4wp\"), not the keys inside it. A form wired to a service (Contact Form 7, WPForms) counts as connected when its account entry exists; the keys inside it are not inspected.<\/li>\n<\/ul>\n\n<h4>0.8.3<\/h4>\n\n<ul>\n<li>The content type list on the export screen shows each type's published count as a number (\"Pages (7 published)\", plus private ones when there are any). It used to join the per-status counts as text (\"Pages (07010000000000)\").<\/li>\n<li>Interface text only an administrator sees is no longer offered for review: an options panel (Redux, Kirki, a theme-options file), a bundled plugin installer (TGM), an <code>admin<\/code> directory, and minified scripts. JavaScript literals that are one lowercase word or a BEM class name (\"fade\", \"collapsing\", \"pswp__img\") are code. Each is still listed in bridge\/hardcoded-text.json with its reason; the same words in a template a visitor sees stay a candidate.<\/li>\n<li>\"Include all undecided\" and \"Exclude all undecided\" decide every text candidate still waiting, on every page, in one step. Menu labels keep their default.<\/li>\n<li>A record with an empty title, or an empty ACF field marked required, no longer stops the export with \"Required content value is empty.\" WordPress saves both: a title-less post, attachment or menu link, and a private or draft post that ACF never held to its required field. An empty title gets a label from the record's slug or id, counted per model in bridge\/warnings.json; an empty required field is left out, becomes optional in that model, and is named in the warnings. No record is dropped. When a required value is still empty, the message names the model, the field and the language.<\/li>\n<\/ul>\n\n<h4>0.8.2<\/h4>\n\n<ul>\n<li>Every REST route with an export id in its path checks, in its own permission callback, that the export is the caller's (the connection key's own, or the signed-in user's), and answers 404 otherwise. This replaces the id check that sat inside the connection key's authorization, which covered key requests only; the handlers still read the export through the same ownership check. Nothing a caller can do changes.<\/li>\n<li>The site title and tagline are the only options read untranslated for language strings, from a fixed list; any other name reads as empty. Same output as 0.8.1.<\/li>\n<\/ul>\n\n<h4>0.8.1<\/h4>\n\n<ul>\n<li>A post or a term nobody has translated is no longer exported as a translation group of one: its language travels on the post or term itself, as before, and the translation groups are only the real ones.<\/li>\n<li>WordPress's own site-building types (navigation menus, template parts, reusable blocks, templates, global styles, fonts) carry their language but form no translation groups: they are not content Contentrain Migrate moves as entries.<\/li>\n<\/ul>\n\n<h4>0.8.0<\/h4>\n\n<ul>\n<li>A multilingual site's languages now travel to Contentrain Migrate as Polylang or WPML declares them: each language's code, locale, tag, name, home address, direction and order, which one is the default, the language's own front page and posts page, and how the site's addresses carry the language (a directory, a subdomain, a domain, or a URL parameter, plus whether the default language is left out of them). Every value is the plugin's own, unchanged.<\/li>\n<li>Categories, tags and other translated terms now carry their language, and a term and its translations travel as one group, as posts and pages already did. A language a group lacks is named.<\/li>\n<li>Polylang's and WPML's own bookkeeping taxonomies (which post is in which language, which ones translate each other) are no longer exported as terms beside your real categories. What they record travels as each post's and term's language and as the translation groups.<\/li>\n<li>Menus carry their language, and with Polylang, the theme locations they are shown in for that language.<\/li>\n<li>Interface strings the plugin translates travel with their translations per language: the site title and tagline, and, with Polylang, the strings themes and plugins register in its Strings screen.<\/li>\n<li>With WPML + ACFML or ACF Options for Polylang, an ACF Options Page's other-language values now travel beside its default-language ones. The Contentrain store keeps its default-language values, as before.<\/li>\n<li>A post's language is now Polylang's or WPML's own code, the same one its translation group uses. With neither plugin nothing changes.<\/li>\n<li>WPML is supported through WPML's own documented filters. There is no WPML licence in this plugin's tests: the WPML path is tested against those filters' documented answers, not against a live WPML site.<\/li>\n<\/ul>\n\n<h4>0.7.1<\/h4>\n\n<ul>\n<li>A request made with a Contentrain Migrate connection key no longer runs as the administrator who created the key. It runs with no WordPress user at all; the key stays valid only while that administrator still has the export and manage_options capabilities, and the exports it starts are recorded in the plugin's own settings, never in a user's profile. A key can no longer reuse or replace an export the administrator started with an application password. Revoking the key also removes the exports it prepared. Nothing changes for Contentrain Migrate: every route, answer and error code is as before.<\/li>\n<li>The Bridge screen, this readme and the privacy-policy text now say what the key lets Migrate read, that it cannot change the site, and how to revoke it.<\/li>\n<li>The export screen sends its requests as plain form fields, each checked on its own, instead of one JSON blob; the REST export routes declare and check every parameter; the note about a server that does not read .htaccess names the kind of server (nginx, IIS, other) instead of repeating the server header. A coding-standards check (PHP_CodeSniffer with the WordPress ruleset) now runs in the plugin's own tests.<\/li>\n<\/ul>\n\n<h4>0.7.0<\/h4>\n\n<ul>\n<li>A new switch under Tools &gt; Contentrain Bridge, \"Let Contentrain Migrate read my pages and media\", lets you open your site to Contentrain Migrate when your robots.txt closes it to crawlers. It is off by default. It changes only the robots.txt WordPress serves, adds one group for Contentrain Migrate, keeps closed what you close for every crawler (wp-admin, for one), and changes nothing for other crawlers. If your robots.txt is a file on disk, or a CDN serves it, the screen shows the lines to add yourself.<\/li>\n<li>Contentrain Migrate can ask the plugin how much a move would carry before you pay: the number of published entries per content type and the number of media files, as WordPress counts them. Only numbers are sent, never titles or addresses. The answer needs the same sign-in as an export (an administrator's application password or your connection key), so it is never public.<\/li>\n<li>The privacy text now says what an export already contained: authors' WordPress usernames as well as their display names, and, when comments are selected, comments of every status (approved, pending, spam and trash), each with its status.<\/li>\n<\/ul>\n\n<h4>0.6.5<\/h4>\n\n<ul>\n<li>The \"modified after\" change list \u2014 the one a reader keeps when it has no earlier inventory to compare with \u2014 now covers every post type the export itself covers, including a custom post type the site keeps out of the REST API (a classic theme's portfolio or product type, which WordPress hides from REST unless the theme says otherwise). Before, an edit to such a record never appeared in that list, although the record itself was exported.<\/li>\n<\/ul>\n\n<h4>0.6.4<\/h4>\n\n<ul>\n<li>The export screen (Tools &gt; Contentrain Bridge) is laid out in WordPress's own cards and shows where the export is: the nine stages as a list with the current one marked, a progress bar with a percentage, the item being worked on, and the counts so far. A stage the chosen scope leaves out (media, comments) is shown as skipped.<\/li>\n<li>When the export stops, the screen says what happened and what to do: a stop the screen can retry (an interrupted or busy request, an archive or delivery that did not advance) offers Retry; an export the server gave up on offers deleting it and starting again with a smaller scope. Keyboard and screen-reader users land on the message, and progress is announced on a stage change and every quarter of the way rather than on every batch.<\/li>\n<li>A finished export opens with its totals and the two ways to take it, the ZIP download and GitHub delivery, with the coverage table folded away until wanted. Before an export the screen says what will happen.<\/li>\n<li>Each stage records its progress (items done, items in all, the latest item) in the export's state, so a screen opened later shows it too. An export started by an earlier version shows its stages without a count until its next stage begins.<\/li>\n<\/ul>\n\n<h4>0.6.3<\/h4>\n\n<ul>\n<li>The export from the plugin's own screen no longer hangs on a host whose time or memory limit ends the request mid-step. Each request now does as many steps as fit in twelve seconds and saves as it goes; a request the host kills is counted, the next one works in smaller batches and saves after every step, an attachment that ends the request again and again is left out with a notice (its WordPress address is kept), and after six killed requests from the same point the export stops with a message saying what to do instead of retrying for ever. The screen retries a busy or interrupted request by itself, with a timeout, and shows the reason when it gives up.<\/li>\n<li>A host where the export's lock cannot be taken for a reason other than another request holding it no longer answers \"busy\" for ever: a marker that expires after two minutes stands in.<\/li>\n<li>The notice at the end of a repeatedly interrupted export names the stage and the step, so a report to the host or to Contentrain carries what is needed.<\/li>\n<\/ul>\n\n<h4>0.6.2<\/h4>\n\n<ul>\n<li>Block themes whose footer is a block pattern (Twenty Twenty-Five's, for one) now export the footer's navigation menus. They sat inside the pattern, which the export did not open, so a migrated site printed the main menu in its footer instead of its footer links. They arrive as before, as menus of the footer.<\/li>\n<li>Advanced Custom Fields fields of a post now carry their type and label in the migration data, as the fields of an options page already did, so a migration no longer has to guess from the value what a field is (an image field holding a number is an image, not text).<\/li>\n<li>When a program such as Contentrain Migrate reads an export through the REST API, an export older than a day now answers \"expired\" and an unknown one \"not found\", instead of a generic bad request, and a request with no sign-in at all is told to use the connection key (some hosts strip the Authorization header). The download of an export you start yourself answers a missing or expired export the same way: \"not found\" or \"expired\", not a generic bad request.<\/li>\n<\/ul>\n\n<h4>0.6.1<\/h4>\n\n<ul>\n<li>Advanced Custom Fields repeaters, groups and flexible content now reach the migration data under their field names (<code>label<\/code>, <code>url<\/code>), not ACF's internal keys (<code>field_5f3a\u2026<\/code>), so they match what the REST API gives and the migrated fields are readable in Studio. Fields on posts and options pages are both affected. A key no field owns is kept as it is and reported.<\/li>\n<\/ul>\n\n<h4>0.6.0<\/h4>\n\n<ul>\n<li>Advanced Custom Fields options pages (\"Site settings\", footer, contact details) are exported as site-wide fields in the migration data, typed like the fields of a post. They were already in the export as their own entry; a migration could not use them until now.<\/li>\n<li>Contentrain Migrate now asks for the \"private content\" scope when it reads an export through the REST API, so drafts, private and password-protected posts stay drafts, pending posts arrive in review, and a scheduled post keeps its publication date. A password-protected post keeps <code>[protected]<\/code>; the password itself never leaves WordPress. The plugin's behaviour is unchanged: the scope is chosen by whoever starts the export, and exports you start yourself are unaffected.<\/li>\n<\/ul>\n\n<h4>0.5.0<\/h4>\n\n<ul>\n<li>Page builders: the Elementor layout tree and page settings, Divi's builder settings, the Elementor kit (global colours, fonts, layout), block-theme global styles and templates are exported without selecting them, so a migration can rebuild the design. Password-protected posts keep none of it.<\/li>\n<li>Page-builder data stays raw evidence and is never turned into content fields. A deeply nested Elementor page no longer stops the whole export as \"too large\"; a value nested past the limit is kept as JSON text with a warning.<\/li>\n<li>Advanced Custom Fields: groups, repeaters, flexible content, links, maps and multiple choices become values inside the entry (objects and lists) instead of separate one-row collections. A page link field no longer fails the export, a link inside a repeater row keeps its label, and fields are judged secret by their type rather than by a broad name rule.<\/li>\n<li>Menus: block themes (such as Twenty Twenty-Five) now export their navigation, including inline links, page lists and home links, and every menu says which theme location shows it. A link to a draft or private page is left out and reported; its child links move up instead of disappearing.<\/li>\n<li>Redirects: rules from <code>.htaccess<\/code> and Simple 301 Redirects are exported, 410 and 451 \"gone\" rules are kept, and Redirection's query, case and trailing-slash options are respected. A rule that depends on a cookie, header, IP or role is reported without its condition data.<\/li>\n<li>A <code>?p=<\/code> link typed with or without <code>www.<\/code> is recognised as the same site.<\/li>\n<li>Tests: ACF, menu and redirect behaviour is checked against the same shared fixtures as Contentrain's WordPress importer, which is updated to 0.5.7.<\/li>\n<\/ul>\n\n<h4>0.4.0<\/h4>\n\n<ul>\n<li>Contentrain Migrate connection key: Migrate can start and read exports on hosts that strip the Authorization header, without an application password. Shown once, HTTPS only, bound to one Migrate order: an hour to paste it, then valid for that order up to 14 days; revocable by you or closed by Migrate.<\/li>\n<li>A password-protected post (exported with private content) carries <code>[protected]<\/code> instead of an empty password, so a reader keeps it off the public site; the password itself still never leaves WordPress.<\/li>\n<li>REST: <code>GET \/about<\/code> says which sign-in a reader can use; <code>POST \/exports\/{id}\/read<\/code> reads a snapshot for a caller whose key travels in the request body; <code>DELETE \/key<\/code> lets Migrate close its own key.<\/li>\n<\/ul>\n\n<h4>0.3.1<\/h4>\n\n<ul>\n<li>Readme: contributors.<\/li>\n<\/ul>\n\n<h4>0.3.0<\/h4>\n\n<ul>\n<li>REST export API for Migrate: start (<code>POST \/exports<\/code>, reused per scope, <code>max_age<\/code>\/<code>fresh<\/code>), list, and advance an export with an application password; read any snapshot file in 8 MiB chunks. Administrator permissions only.<\/li>\n<li>The snapshot of a REST export carries its RawIR v1 as <code>bridge\/rawir.json<\/code>. GitHub delivery never includes it.<\/li>\n<li><code>media_files: false<\/code> (REST, and a checkbox on the export screen): attachment records without their files; content keeps the WordPress upload URLs.<\/li>\n<li>SEO: Rank Math, AIOSEO, SEOPress and Yoast title and description templates are rendered to the text a page carries, with robots and canonical; SEOPress support; AIOSEO Pro term values. Checked against each plugin running live.<\/li>\n<li>Large sites: the export's memory no longer grows with the site. 5,000 posts, 20,000 media records and 20,000 comments export in about two minutes at a 64 MB memory limit.<\/li>\n<li>Shared hosts: an advance stops before PHP's max_execution_time and saves as it goes, and a request killed midway writes nothing twice.<\/li>\n<li>A snapshot being downloaded is not replaced by a fresh export for ten minutes.<\/li>\n<li>Updates are manual outside WordPress.org: install the new release ZIP over the old one.<\/li>\n<li>Readme: what the plugin requests from this site and from GitHub, and the REST export API, described for WordPress.org.<\/li>\n<\/ul>\n\n<h4>0.2.1<\/h4>\n\n<ul>\n<li>Prevent partial ACF modelling, disambiguate same-name fields and normalize stored dates.<\/li>\n<li>Protect nested sensitive ACF values and private attachment scope.<\/li>\n<li>Fix nested-page media URLs, binary export reads and repeated GitHub delivery manifests.<\/li>\n<li>Clean up expired and uninstalled snapshots; serialize cancellation with active export work.<\/li>\n<li>Add a checksum-verified Bridge-to-Migrate intake adapter and reproducible package build.<\/li>\n<\/ul>\n\n<h4>0.2.0<\/h4>\n\n<ul>\n<li>ACF field groups become Contentrain models: a repeater is a collection of records, a group is a related record, and sub-field types map onto Contentrain field types.<\/li>\n<li>Media transfer: uploads and their generated sizes are copied into <code>media\/<\/code> and content is relinked to them.<\/li>\n<li>Contentrain models: JSON collections, Markdown documents, a site singleton and an interface-text dictionary.<\/li>\n<li>Interface-text discovery in theme and plugin source, with a mandatory review step before anything is exported.<\/li>\n<li>Bounded, resumable export with validation before delivery.<\/li>\n<li>Free ZIP download and free delivery to a branch in your own GitHub repository.<\/li>\n<li>Metadata is opt-in, and secret-looking keys and values are excluded.<\/li>\n<\/ul>\n\n<h4>0.1.0<\/h4>\n\n<ul>\n<li>Initial local RawIR v1 export.<\/li>\n<\/ul>","raw_excerpt":"Export posts, pages, media and ACF fields as Markdown\/JSON to a ZIP or your GitHub repo, ready for Astro or any framework that reads Markdown.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/375359","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=375359"}],"author":[{"embeddable":true,"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/abb65"}],"wp:attachment":[{"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=375359"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=375359"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=375359"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=375359"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=375359"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/hau.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=375359"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}