{"id":133443,"date":"2020-12-11T21:00:03","date_gmt":"2020-12-11T21:00:03","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/substack-importer\/"},"modified":"2026-04-22T09:17:35","modified_gmt":"2026-04-22T09:17:35","slug":"substack-importer","status":"publish","type":"plugin","link":"https:\/\/ur.wordpress.org\/plugins\/substack-importer\/","author":5911429,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_crdt_document":"","version":"1.2.0","stable_tag":"1.2.0","tested":"6.9.4","requires":"5.2","requires_php":"7.4","requires_plugins":null,"header_name":"Substack Importer","header_author":"wordpressdotorg","header_description":"Import posts from a Substack export file.","assets_banners_color":"","last_updated":"2026-04-22 09:17:35","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/wordpress\/substack-importer","header_author_uri":"","rating":2,"author_block_rating":0,"active_installs":900,"downloads":13182,"num_ratings":3,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.1.0":{"tag":"0.1.0","author":"jeroenpfeil","date":"2021-05-06 11:23:52"},"1.0.0":{"tag":"1.0.0","author":"jeroenpfeil","date":"2021-05-20 06:52:35"},"1.0.1":{"tag":"1.0.1","author":"jeroenpfeil","date":"2021-07-12 11:35:32"},"1.0.2":{"tag":"1.0.2","author":"jeroenpfeil","date":"2021-07-12 11:42:47"},"1.0.3":{"tag":"1.0.3","author":"jeroenpfeil","date":"2021-08-11 05:08:27"},"1.0.4":{"tag":"1.0.4","author":"jeroenpfeil","date":"2021-08-31 06:13:13"},"1.0.5":{"tag":"1.0.5","author":"jeroenpfeil","date":"2022-12-05 10:13:09"},"1.0.6":{"tag":"1.0.6","author":"jeroenpfeil","date":"2023-05-10 07:54:33"},"1.0.7":{"tag":"1.0.7","author":"jeroenpfeil","date":"2023-08-10 13:50:50"},"1.0.8":{"tag":"1.0.8","author":"jeroenpfeil","date":"2023-08-11 12:49:59"},"1.0.9":{"tag":"1.0.9","author":"jeroenpfeil","date":"2023-12-28 03:00:15"},"1.1.0":{"tag":"1.1.0","author":"jeroenpfeil","date":"2024-02-15 07:38:11"},"1.1.1":{"tag":"1.1.1","author":"dotcomcaribou","date":"2024-10-23 07:00:39"},"1.2.0":{"tag":"1.2.0","author":"githubsync","date":"2026-04-22 09:17:35"}},"upgrade_notice":[],"ratings":{"1":1,"2":1,"3":1,"4":0,"5":0},"assets_icons":{"icon-256x256.png":{"filename":"icon-256x256.png","revision":2527220,"resolution":"256x256","location":"assets","locale":""}},"assets_banners":[],"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.1.0","1.0.0","1.0.1","1.0.2","1.0.3","1.0.4","1.0.5","1.0.6","1.0.7","1.0.8","1.0.9","1.1.0","1.1.1","1.2.0"],"block_files":[],"assets_screenshots":[],"screenshots":[],"jetpack_post_was_ever_published":false},"plugin_section":[],"plugin_tags":[6606,194015],"plugin_category":[59],"plugin_contributors":[77594],"plugin_business_model":[],"class_list":["post-133443","plugin","type-plugin","status-publish","hentry","plugin_tags-importer","plugin_tags-substack","plugin_category-utilities-and-tools","plugin_contributors-wordpressdotorg","plugin_committers-githubsync","plugin_committers-ninomiho","plugin_committers-zaerl"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/substack-importer\/assets\/icon-256x256.png?rev=2527220","icon_2x":"https:\/\/ps.w.org\/substack-importer\/assets\/icon-256x256.png?rev=2527220","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>The Substack Importer will import content from an export file downloaded from your Substack newsletter.<\/p>\n\n<p>The following content will be imported:<\/p>\n\n<ul>\n<li>Posts and images.<\/li>\n<li>Podcasts.<\/li>\n<li>Comments (only for publicly accessible posts).<\/li>\n<li>Author information.<\/li>\n<\/ul>\n\n<p>In the future, we plan to improve the importer by:<\/p>\n\n<ul>\n<li>Mailing lists.<\/li>\n<li>Enhancing the performance of processing export files with many posts and media.<\/li>\n<\/ul>\n\n<h3>Development<\/h3>\n\n<p>For running unit tests and contributing to the plugin, see the <a href=\"https:\/\/github.com\/wordpress\/substack-importer#development\">README on GitHub<\/a>.<\/p>\n\n<p>Tests can be run with wp-env or with any local WordPress setup paired with a Docker MySQL container. Run <code>composer install<\/code> first, then <code>vendor\/bin\/phpunit<\/code>.<\/p>\n\n<h3>Hooks<\/h3>\n\n<p>The Substack Importer provides filters and actions at key stages of the content conversion pipeline.<\/p>\n\n<h4>Post-level Filters<\/h4>\n\n<h3>substack_importer_post_meta<\/h3>\n\n<p>Filter the post metadata loaded from the Substack API before it is used for author, comments, and other post data.<\/p>\n\n<p>Parameters:\n* <code>$post_meta<\/code> (array|null) - The post metadata from the Substack API response.\n* <code>$post<\/code> (array) - The raw Substack post data from the CSV.\n* <code>$id<\/code> (int) - The Substack post ID.<\/p>\n\n<h3>substack_importer_raw_content<\/h3>\n\n<p>Filter the raw HTML content before Gutenberg conversion. Runs after the subtitle has been prepended (if present). Useful for cleaning up Substack-specific HTML, adding custom elements, or stripping unwanted markup.<\/p>\n\n<p>Parameters:\n* <code>$html_body<\/code> (string) - The raw HTML content from the Substack export.\n* <code>$post<\/code> (array) - The raw Substack post data from the CSV.\n* <code>$post_meta<\/code> (array|null) - The post metadata from the Substack API response.<\/p>\n\n<h3>substack_importer_subtitle<\/h3>\n\n<p>Filter the subtitle HTML before it is prepended to the post content. Return an empty string to skip the subtitle entirely.<\/p>\n\n<p>Parameters:\n* <code>$heading<\/code> (string) - The subtitle HTML (default: an h2 element).\n* <code>$post<\/code> (array) - The raw Substack post data.<\/p>\n\n<h3>substack_importer_post_content_after_conversion<\/h3>\n\n<p>Filter the post content after Gutenberg conversion but before it is added to the WXR. Useful for wrapping paywalled content in custom blocks (e.g., membership plugins).<\/p>\n\n<p>Parameters:\n* <code>$post_content<\/code> (string) - The converted Gutenberg block content.\n* <code>$post<\/code> (array) - The original Substack post data.\n* <code>$post_meta<\/code> (array|null) - Additional post metadata from Substack API.<\/p>\n\n<h3>substack_importer_post_data<\/h3>\n\n<p>Filter the final post data array before it is added to the WXR.<\/p>\n\n<p>Parameters:\n* <code>$post_data<\/code> (array) - The post data.\n* <code>$post<\/code> (array) - The original Substack post data.<\/p>\n\n<h4>Content Conversion Filters<\/h4>\n\n<h3>substack_importer_converted_node<\/h3>\n\n<p>Filter the result of a single node conversion to a Gutenberg block. Allows modification of the block name and attributes. Return a null block_name to skip the node.<\/p>\n\n<p>Parameters:\n* <code>$block_data<\/code> (array) - Array with 'block_name' and 'block_attributes' keys.\n* <code>$node<\/code> (DOMElement) - The converted DOM node.\n* <code>$node_name<\/code> (string) - The original HTML tag name (e.g. 'p', 'div', 'h2').<\/p>\n\n<h3>substack_importer_image_result<\/h3>\n\n<p>Filter the image node conversion result. Useful for adjusting image sizes, captions, or link destinations.<\/p>\n\n<p>Parameters:\n* <code>$result<\/code> (array) - Array with 'block_attributes' and 'node' keys.\n* <code>$image_data<\/code> (array|null) - The decoded image data from the Substack data-attrs attribute.<\/p>\n\n<h3>substack_importer_pre_embed_conversion<\/h3>\n\n<p>Short-circuit the embed node conversion before default handling. Return a non-null array to skip the built-in switch statement entirely. Useful for handling unsupported embed types or overriding the default conversion for a specific provider.<\/p>\n\n<p>Parameters:\n* <code>$pre_result<\/code> (array|null) - Return non-null to short-circuit. Expected keys: 'node', 'block_attributes', 'block_name'.\n* <code>$node<\/code> (DOMElement) - The embed DOM node before conversion.\n* <code>$parent<\/code> (DOMElement) - The parent DOM element.\n* <code>$first_class<\/code> (string) - The CSS class identifying the embed type (e.g. 'youtube-wrap', 'tweet').<\/p>\n\n<h3>substack_importer_embed_result<\/h3>\n\n<p>Filter the embed node conversion result after the default conversion. Useful for modifying embed URLs, adding custom attributes, or changing how embeds are represented.<\/p>\n\n<p>Parameters:\n* <code>$output<\/code> (array) - Array with 'block_name', 'block_attributes', and 'node' keys.\n* <code>$first_class<\/code> (string) - The CSS class identifying the embed type.<\/p>\n\n<h3>substack_importer_audio_block<\/h3>\n\n<p>Filter the Gutenberg audio block HTML for podcast posts.<\/p>\n\n<p>Parameters:\n* <code>$block<\/code> (string) - The Gutenberg audio block HTML.\n* <code>$audio_url<\/code> (string) - The URL of the podcast audio file.<\/p>\n\n<h4>Paywall Filters<\/h4>\n\n<h3>substack_importer_paywall_marker_text<\/h3>\n\n<p>Filter the paywall marker text that appears in the imported content.<\/p>\n\n<p>Parameters:\n* <code>$marker_text<\/code> (string) - The default paywall marker text.\n* <code>$node<\/code> (DOMElement) - The paywall node being converted.\n* <code>$parent<\/code> (DOMElement) - The parent element.<\/p>\n\n<h3>substack_importer_paywall_content<\/h3>\n\n<p>Filter the entire paywall conversion result. Return a non-null value to override the default conversion.<\/p>\n\n<p>Parameters:\n* <code>$result<\/code> (array|null) - The conversion result, null to use default.\n* <code>$node<\/code> (DOMElement) - The paywall node being converted.\n* <code>$parent<\/code> (DOMElement) - The parent element.<\/p>\n\n<h4>Actions<\/h4>\n\n<h3>substack_importer_before_post<\/h3>\n\n<p>Fires before a single Substack post is processed and converted. Useful for setting up state or performing actions before conversion begins.<\/p>\n\n<p>Parameters:\n* <code>$post<\/code> (array) - The raw Substack post data from the CSV.\n* <code>$post_meta<\/code> (array|null) - The post metadata from the Substack API response.\n* <code>$id<\/code> (int) - The Substack post ID.<\/p>\n\n<h3>substack_importer_after_post<\/h3>\n\n<p>Fires after a single Substack post has been converted and added to the WXR. Useful for logging, progress tracking, or performing cleanup after each post.<\/p>\n\n<p>Parameters:\n* <code>$post_data<\/code> (array) - The final post data that was added to the WXR.\n* <code>$post<\/code> (array) - The raw Substack post data from the CSV.\n* <code>$post_meta<\/code> (array|null) - The post metadata from the Substack API response.\n* <code>$id<\/code> (int) - The Substack post ID.<\/p>\n\n<!--section=installation-->\n<p>This plugin depends on the <a href=\"https:\/\/wordpress.org\/plugins\/wordpress-importer\">WordPress Importer<\/a> plugin which needs to be installed first.<\/p>\n\n<p>To install the Substack Importer:<\/p>\n\n<ol>\n<li>Upload the <code>substack-importer<\/code> directory to the <code>\/wp-content\/plugins\/<\/code> directory<\/li>\n<li>Activate the plugin through the 'Plugins' menu in WordPress<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"after%20about%2030%20seconds%2C%20the%20import%20stops%20and%20i%20am%20seeing%20a%20blank%20screen.%20what%20happened%3F\"><h3>After about 30 seconds, the import stops and I am seeing a blank screen. What happened?<\/h3><\/dt>\n<dd><p>When trying to import a large number of posts and images, timeouts can occur. To solve this, you can try to run the import\nseveral times until all content has been imported.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>Compatibility: the plugin now requires PHP 7.4 or higher.<\/li>\n<li>Enhancement: added new pre-import options for forcing Draft status, choosing publish date mode, setting the first image as Featured Image, and applying a global Category\/Tag.<\/li>\n<li>Enhancement: improved import behavior handling for featured image assignment and post metadata processing during import.<\/li>\n<li>Enhancement: added <code>substack_importer_paywall_marker_text<\/code> filter to customize paywall marker text.<\/li>\n<li>Enhancement: added <code>substack_importer_paywall_content<\/code> filter to override paywall block conversion.<\/li>\n<li>Enhancement: added <code>substack_importer_post_content_after_conversion<\/code> filter to modify content after Gutenberg conversion.<\/li>\n<li>Enhancement: added <code>substack_importer_raw_content<\/code> filter to modify raw HTML before Gutenberg conversion.<\/li>\n<li>Enhancement: added <code>substack_importer_subtitle<\/code> filter to customize or skip the subtitle heading.<\/li>\n<li>Enhancement: added <code>substack_importer_post_meta<\/code> filter to modify post metadata before processing.<\/li>\n<li>Enhancement: added <code>substack_importer_converted_node<\/code> filter to customize individual block conversions.<\/li>\n<li>Enhancement: added <code>substack_importer_image_result<\/code> filter to modify image block attributes.<\/li>\n<li>Enhancement: added <code>substack_importer_embed_result<\/code> filter to modify embed block results after conversion.<\/li>\n<li>Enhancement: added <code>substack_importer_pre_embed_conversion<\/code> filter to short-circuit embed conversion before default handling.<\/li>\n<li>Enhancement: added <code>substack_importer_audio_block<\/code> filter to customize the podcast audio block.<\/li>\n<li>Enhancement: added <code>substack_importer_before_post<\/code> action that fires before each post is processed.<\/li>\n<li>Enhancement: added <code>substack_importer_after_post<\/code> action that fires after each post is added to the WXR.<\/li>\n<\/ul>\n\n<h4>1.1.2<\/h4>\n\n<ul>\n<li>Enhancement: support captions for images.<\/li>\n<li>Enhancement: support TikTok embeds<\/li>\n<li>Compatibility: the plugin now requires PHP 7.2 or higher.<\/li>\n<li>Fix: convert preformatted content to verse block.<\/li>\n<li>Fix: twitter conversion bug.<\/li>\n<\/ul>\n\n<h4>1.1.1<\/h4>\n\n<ul>\n<li>Tested up to WordPress 6.7<\/li>\n<li>Fix: null checking<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Update <code>wxr-generator<\/code> to latest version. Fixes a bug where imports could error out due to a misformed timezone identifier.<\/li>\n<\/ul>\n\n<h4>1.0.9<\/h4>\n\n<ul>\n<li>Use subtitle as post excerpt if not empty<\/li>\n<li>Testing the plugin up to WordPress 6.4.2<\/li>\n<li>Fix PHPCS error and cleanup composer.lock<\/li>\n<\/ul>\n\n<h4>1.0.8<\/h4>\n\n<ul>\n<li>Removed the subscription input from post content<\/li>\n<\/ul>\n\n<h4>1.0.7<\/h4>\n\n<ul>\n<li>Convert the paywall div to a paragraph<\/li>\n<\/ul>\n\n<h4>1.0.6<\/h4>\n\n<ul>\n<li>Testing the plugin up to WordPress 6.2<\/li>\n<\/ul>\n\n<h4>1.0.5<\/h4>\n\n<ul>\n<li>Add support for WordPress 6.1<\/li>\n<\/ul>\n\n<h4>1.0.4<\/h4>\n\n<ul>\n<li>Fix Soundcloud embeds<\/li>\n<\/ul>\n\n<h4>1.0.3<\/h4>\n\n<ul>\n<li>Identify authors for draft posts as \"Draft Posts\"<\/li>\n<\/ul>\n\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>Republishing to fix a CI error.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Remove unnecessary load_meta_data line.<\/li>\n<li>Fix embeds not displaying properly on website.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Add post meta for paid content.<\/li>\n<li>Convert Instagram embed to a link.<\/li>\n<li>Add the subtitle as a H2 at the beginning of the post.<\/li>\n<li>Set the correct comment_status for posts.<\/li>\n<\/ul>\n\n<h4>0.1.0<\/h4>\n\n<ul>\n<li>Refactored the importer.<\/li>\n<li>Add support for authors.<\/li>\n<li>Add support for comments.<\/li>\n<li>Conversion of content to Gutenberg blocks.<\/li>\n<li>Convert the export to WXR and use the WordPress Importer plugin to import the WXR.<\/li>\n<li>Add progress indicator<\/li>\n<li>Add support for attachments.<\/li>\n<\/ul>\n\n<h4>0.1<\/h4>\n\n<p>Early proof-of-concept version.<\/p>","raw_excerpt":"The Substack Importer allows you to import content from a Substack newsletter into your WordPress site.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/133443","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"author":[{"embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/users\/5911429"}],"replies":[{"embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=133443"}],"wp:attachment":[{"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=133443"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=133443"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=133443"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=133443"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=133443"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=133443"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}