days since this article was written, please be aware of its timeliness
Some of the workflow solutions described in this article are outdated. The new workflow logic can be found here: https://www.xheldon.com/tech/my-blog-ci-in-2022.html
Spent two days figuring out a simple way to write content in Craft, sync it to a Github repository via a custom plugin, and have the Github repository automatically build and deploy to Github Pages.
Repository address: https://github.com/craft-extension/craft-github-extension
Changes to the Blog Update Process
Previous Workflow
Write in VSCode → Commit to x_blog_src repository → Trigger Github Action → Build and commit to x_blog repository → Success
However, this process had some pain points:
-
You might not finish an article in one sitting but write intermittently. If you wrote part of it on your work computer, you’d need to commit to Github first, then continue writing on your home computer. Each commit would trigger the Github Action. My solution was to check the commit message for a specific prefix—commits with the prefix would trigger a build, while those without would be ignored. Still, this wasn’t very elegant.
-
Another approach was to write in the _draft directory first, allowing free commits, and then move the article to _post once finished. But as mentioned earlier, this still wasn’t elegant.
-
A third option was to configure Jekyll to exclude future-dated posts. You could set the meta date to something like 2099 while drafting, and Jekyll wouldn’t build it. However, this would still trigger the Github Action and update files—again, not elegant.
-
Sometimes, I was tempted by Craft’s beauty (great aesthetics) and functionality (editing anytime, anywhere, and multi-device sync). I’d write articles in Craft first, export them as Markdown, manually upload images to the x_blog-static repository, update the image links in the Markdown, and then commit to Github. This process was way too long.
Current Workflow
Write in Craft → Commit to x_blog_src repository → Trigger Github Action → Build and commit to x_blog repository → Success
The key difference from before is switching from writing in VSCode to writing directly in Craft. As mentioned in the fourth point of the previous workflow, this lets me enjoy Craft’s benefits while eliminating the hassle of syncing to Github. Aside from images (more on that below), syncing text content alone is a perfect solution. Now, I might update the blog more frequently (previously, the complexity slowed me down) and focus more on content. Awesome!
Notes
Here are the usage settings:
-
This plugin is currently for personal use only, so some configurations are hardcoded for individual needs. For example, it only supports uploading files to the
_postsdirectory. If there are more users, we may invest more time to make it more generic. For now, since it’s just for personal use, it’s not as refined. -
A Github Personal Token is required to sync Markdown content to a specified Github repository via Github’s Rest API.
-
When sending content to the repository, the Github API compares the SHA values of the source and target repositories. If they are identical, the updated file won’t display commit information, and checking the commit log will show “0 files changed.”
-
The first block of an article must be a two-column table serving as Jekyll’s meta information. Currently, the meta includes all content except the path.
-
The article title serves as the blog post title and doesn’t need to be included in the meta.
-
As of December 9, 2021, testing shows that the Markdown output from Craft’s API
craftBlockToMarkdownfails to render Blockquote styles (called “Focus” under Decorations in Craft Blocks—officially acknowledged as a bug to be fixed in a later version). If you want custom Markdown, you’ll need to traverse the Blocks yourself. Other features remain untested. -
Currently, the plugin’s configuration options aren’t very user-friendly and will be optimized later.
-
Craft’s Markdown formatting is visually appealing—for example, you can embed a paragraph or image under a list to align with the list content. However, standard Markdown doesn’t support this, so the output styling may look less polished.
-
If meta contains multi-line information like tags, you can separate them with
-:, e.g.,-:test-:server. -
For Craft’s Markdown API, it’s recommended to use the
commonformat (default, currently non-configurable in this plugin). If you use thebearformat, images won’t be prefixed with!, causing Github to treat them as links instead of images. -
Some Craft features like “Deep Markdown Links” aren’t part of standard Markdown syntax, so support for them hasn’t been tested here.
-
According to the official documentation, the storageApi is not encrypted on the Web side, so users should be reminded to be cautious when storing data. See here. This issue does not exist on Mac.
Analysis revealed that the storageApi on the Web was stored in SessionStorage. Upon further review, it was later changed to IndexedDB (after all, it’s a developer preview version, and changes are made quickly…), specifically in a DB namedplugindata-storage. As for the Mac version, because I switched the user workspace, the Storage was also lost. I reported this issue, but the official team has not responded yet :-(. -
The storageApi on Mac has a race condition issue. Therefore, if you attempt to read data using storageApi immediately upon loading, it will fail. To address this, the logic in my plugin is to delay plugin initialization by 3 seconds for Mac, while the Web version has no such restriction.
-
More configuration support coming soon…
About Image Issues
-
Whether on the Web or desktop, images uploaded to Craft are displayed in
.jpgformat when inspected via element inspection on the Web side. However, after generating the content using Craft’s Markdown API,it was mistakenly thought to change to. This was a misunderstanding—the image extension remains consistent with the format at upload time; Craft does not convert the format..pngformat -
Although the image URLs displayed in the documentation’s element inspection use the domain
res.craft.do, and the source files in the Github repository also show this address, when previewing images in Github (i.e., directly opening the repository’s md file), the address changes tohttps://camo.githubusercontent.com. Initially, it might seem that Github kindly hosts your md images on its servers, but in reality, Github temporarily caches third-party images for security reasons during rendering. -
Without relying on a server, images uploaded to Craft cannot be fetched using frontend methods like
fetchwhen retrieved via the Craft API, as it will result in CORS errors. Therefore, if you want to extract images from an article when uploading it to Github and host them on your own image bed/COS/Github repository, a pure frontend solution is insufficient. You need to use a server to pull and rehost the images. Additionally, Craft imageBlock image URLs are hosted on AWS S3. Pulling them from domestic servers like Tencent Cloud may time out or be slow, but pushing from overseas servers to domestic ones seems relatively fast. Thus, it’s recommended to use AWS services for image rehosting (Update: I currently use Vercel; details can be found in this article).
Here’s an image to test the speed:

More
-
Planning to develop a template plugin, available at: https://github.com/craft-extension/craft-template-extension
-
All plugins are developed based on this tool: https://github.com/craft-extension/craft-dev-toolkit Added some configurations like using antd for UI on top of the official examples. If there are users in the future, I’ll consider adding more configurations, such as supporting scaffold generation for plugin development environments.
-
Stay tuned for more plugins…
Update 2022.01.24
Regarding the image issue, one idea is to stop backing up images on Github (since images in Craft essentially serve as backups) and directly upload them to Tencent Cloud COS. However, Tencent Cloud COS requires sending requests using the request library, which is wrapped with additional layers to include signatures and other information. After examining the COS source code, I found it allows configuration of the request tool. Currently, I have two approaches to achieve this:
-
Override the request library methods used by COS.
-
Rewrite the request code within COS.
-
Construct signatures manually according to the documented examples to make requests.
Obviously, the third method was ultimately chosen for its simplicity.
Update 2022.02.28
The 01.24 solution has been abandoned due to high costs. The final approach is to stop uploading images to Github and instead use Vercel’s service to store them on Tencent Cloud. This resolves the image issue, allowing for seamless article writing in Craft.
I often wish that when facing some key decisions in life, someone could tell me the best course of action so that I would not waste my precious time. Putting myself in others' shoes, I therefore write blogs often, hoping to record in this tiny corner of the vast Internet the once-in-a-lifetime experiences that matter to me, and to help those who seek help.