brightdigit/contributewordpress
Import your WordPress site into Publish.
What is ContributeWordPress?
Migrating off WordPress means moving two things: the posts and the media that goes with them. WordPress hands you the first as a WXR export — one or more XML files full of HTML post bodies — and leaves the second sitting in wp-content/uploads on a server you're about to turn off.
ContributeWordPress turns that pair into a ready-to-build Publish site: Markdown files with YAML front matter in your Content/ directory, images copied or downloaded into your Resources/ directory with their URLs rewritten to match, and — optionally — a redirect file so your old permalinks keep working.
It is built on Contribute (the generic source → markdown pipeline) and SyndiKit (which decodes the WXR file). Multi-site exports are supported: point it at a directory of XML files and each site maps to its own section. brightdigit.com was migrated this way — brightdigit.com into /articles and learningswift.brightdigit.com into /tutorials.
The package ships a library and a small wpublish executable that wraps it.
Installation
Add ContributeWordPress to your Package.swift:
dependencies: [
.package(url: "https://github.com/brightdigit/ContributeWordPress.git", from: "1.0.0-alpha.1")
]Then add it to a target:
.target(
name: "MySiteImporter",
dependencies: [.product(name: "ContributeWordPress", package: "ContributeWordPress")]
)Usage
Exporting from WordPress
ContributeWordPress needs a backup XML file (WXR) from your WordPress site:
- Log in to your WordPress Administration Dashboard.
- Go to Tools → Export:
[WordPress Tools and Export]
- Download your export file by either clicking Export All, selecting all content and
clicking Download Export File, or exporting specific content only (posts, pages, feedback).
- You'll get a
.zipcontaining the.xmlfile — your posts, pages, comments, categories,
tags, and references to your site's images.
Larger sites export as more than one XML file; ContributeWordPress imports a whole directory of them. For more detail see the WordPress export documentation.
You also need access to the images, by either downloading them from the live site or keeping a copy of the site's files:
[Example copy of the WordPress site's files]
Importing into Publish
The simplest import passes the path to your XML files and the root of your Publish site:
import ContributeWordPress
let fromURL = URL(fileURLWithPath: "directory/containing/your/export/xml/files")
let toURL = URL(fileURLWithPath: "Path/to/Publish/root")
try MarkdownProcessor.beginImport(from: fromURL, to: toURL)The bundled wpublish executable does exactly this from the command line:
wpublish \
directory/with/export/xml/files \
Path/to/Publish/root \
path/to/root/wordpress/files # optionalDownloading vs. copying assets
By default ContributeWordPress downloads images from the live WordPress site, rewriting each reference to its new location. So HTML in your export like:
<figure class="wp-block-image"><img src="https://leogdion.name/wp-content/uploads/2019/01/image-1024x682.jpg" class="wp-image-105"/></figure>becomes, in your markdown file:
<figure class="wp-block-image">
<img src="/media/wp-assets/default/2019/01/image-1024x682.jpg" class="wp-image-105" />
</figure>If the site is already offline but you have a local copy of its files, pass that directory instead. AssetImportSetting has three cases:
none— do nothingdownload— fetch them from the live WordPress site (the default)copyFilesFrom(URL)— copy them from a local directory
try MarkdownProcessor.beginImport(
from: fromURL,
to: toURL,
importAssetsBy: .copyFilesFrom(URL(fileURLWithPath: "path/to/wordpress/files"))
)Converting HTML to Markdown
By default the post body is passed through unchanged (PassthroughMarkdownGenerator). To actually convert the WordPress HTML, use SwiftSoupMarkdownGenerator from the Contribute library — it converts in-process with SwiftSoup and swift-markdown, so there is no pandoc to install and it works on Linux and CI:
try MarkdownProcessor.beginImport(
from: fromURL,
to: toURL,
usingGenerator: SwiftSoupMarkdownGenerator(),
importAssetsBy: importAssetsSetting
)Any type conforming to Contribute's MarkdownGenerator works, and HTMLtoMarkdown wraps a plain closure:
public protocol MarkdownGenerator {
func markdown(fromHTML htmlString: String) throws -> String
}Filtering posts
A WXR export contains far more than published posts. By default the import keeps only published posts, via RegexKeyPostFilter, which pairs a KeyPath on SyndiKit's WordPressPost with a regular expression:
[
RegexKeyPostFilter(pattern: "post", keyPath: \.type),
RegexKeyPostFilter(pattern: "publish", keyPath: \.status)
]Supply your own PostFilter values to change that:
try MarkdownProcessor.beginImport(
from: fromURL,
to: toURL,
filteringPostsWith: myFilters,
importAssetsBy: importAssetsSetting
)Redirecting old URLs
Migrating permalinks usually means emitting a redirect file. Implement RedirectFormatter:
formatRedirects(_:)— takes theRedirectItemvalues (old URL, new URL) and returns the file
contents.
redirectsURL(basedOnResourcesDirectoryURL:)— returns where that string is written.
No redirect file is written unless you supply a formatter. NetlifyRedirectFormatter ships in the box; RedirectFileWriter gives finer control.
Parsing settings
For anything beyond two or three arguments, use Swift Argument Parser rather than reading CommandLine directly — see this example.
Full API documentation is on the Swift Package Index.
Requirements
- Swift 6.4+
- macOS 15+, iOS 16+, tvOS 16+, watchOS 9+
- Linux (Ubuntu 24.04 "Noble"), Windows, and Android are covered by CI
License
MIT © BrightDigit
Package Metadata
Repository: brightdigit/contributewordpress
Default branch: main
README: README.md