If you write your documentation in Markdown and store it on GitHub, Knowledge Base Pro can keep it in sync with your knowledge base in both directions — pull .md files in as KB articles, and push article edits back out as commits. No copy-pasting required.

This guide starts with the everyday setup steps and then goes deeper into the developer reference: every frontmatter field, configuration options, webhook and REST endpoints, developer hooks, and the post meta schema generated by the sync engine.

How it works

The sync engine fetches Markdown files from a GitHub repository via the Git Trees API, converts them to Gutenberg blocks (or classic HTML), and creates or updates wz_knowledgebase posts. Each synced article stores metadata linking it back to its source file, so subsequent syncs only re-process files whose SHA (the unique fingerprint GitHub assigns to every file version) has changed.

The integration works in both directions:

  • Import (GitHub → WordPress) — run on demand from the import wizard, or automatically via a push webhook.
  • Export/push-back (WordPress → GitHub) — push a single article from its editor, push automatically on save, or bulk-push a whole mapping from the export wizard.

External images referenced in Markdown are optionally sideloaded into the WordPress Media Library, and relative .md links are rewritten to WordPress post permalinks automatically. The Markdown converter also understands GitHub AlertsOpens in a new window, markers, tables, and fenced code blocks with language hints.

Five types of alerts are available:

> [[!NOTE]]
> ⓘ Useful information that users should know, even when skimming content.

> [[!TIP]]
> ✅ Helpful advice for doing things better or more easily.

> [[!IMPORTANT]]
> ❗ Key information users need to know to achieve their goal.

> [[!WARNING]]
> ⚠️ Urgent info that needs immediate user attention to avoid problems.

> [[!CAUTION]]
> ⚠️ Advises about risks or negative outcomes of certain actions.

What you need before you start

  • A GitHub repository containing your .md documentation files.
  • Knowledge Base Pro is installed and activated.
  • Administrator access to your WordPress site.

Step 1 — Prepare your Markdown files

Each Markdown file becomes a KB article. Add a short block of settings at the very top of each file, between — lines. This is called frontmatter and specifies the title, section, and status to use.

---
title: "Getting Started"
sections: [[Installation]]
status: publish
---

Your article content starts here...

The only thing frontmatter does is give the plugin a few hints. Everything else — headings, lists, images, code blocks — is pulled in from your Markdown as-is. All fields are optional; if you skip the title, the filename is used, and if you skip sections, the article is imported without a section.

Frontmatter field reference

FieldAliasesTypeWhat it does
titlestringArticle title shown in the KB. Defaults to the filename stem.
slugstringThe URL slug. Defaults to the filename stem.
sectionscategories, category, sectioncomma-separated listwzkb_category terms. Supports path notation for hierarchy (see below). Missing terms are created automatically.
tagstagcomma-separated listwzkb_tag terms. Missing terms are created automatically.
productsproductcomma-separated listwzkb_product terms. Falls back to the mapping’s configured product.
ordermenu_orderintegerSort order within a section (menu_order).
statusstringpublish, draft, pending, or private. Overridden by the mapping’s Article Status setting when that is set.
tocbooleanInsert a table-of-contents block before the first heading when no [toc] marker is present in the body.
featured_imagethumbnail, cover, imagestringSets the article’s featured image. Accepts an absolute URL or a path relative to the Markdown file (e.g. images/hero.png). Requires Import external media to be enabled. See Featured images below.
idintegerOptional stable document ID. Stored as _wzkb_github_doc_id.

sections, tags, and products take a comma-separated list wrapped in square brackets, e.g. tags: [[setup, beginner]]. A single value still uses the brackets: tags: [[setup]].

How terms and capitalization are handled

Each value is matched to an existing term by its slug, which is always lower-cased (spaces and punctuation become hyphens). So Installation, installation, and INSTALLATION all resolve to the same installation term — capitalization never creates duplicates. The exact text you type is used as the term’s display name only when the term does not yet exist and has to be created. In practice:

  • To reference an existing term, use its slug (lower-case), e.g. sections: [[getting-started]].
  • To create a new term, type the name as you want it displayed, e.g. sections: [[Getting Started]] creates a term named “Getting Started” with slug getting-started.

This means the following two lines are equivalent — both target the same installationadvanced and troubleshooting terms:

sections: [[Installation/Advanced, Troubleshooting]]
sections: [[installation/advanced, troubleshooting]]

The only difference is when a term is first created: the capitalized form produces display names like “Installation”, while the lowercase form produces “installation”. If the terms already exist, the casing you use is ignored.

A full example

---
slug: advanced-configuration
title: "Advanced Configuration"
products: [[knowledgebase, my-plugin]]
sections: [[Installation/Advanced, Troubleshooting]]
tags: [[configuration, advanced, beginner]]
status: publish
order: 5
toc: true
featured_image: images/advanced-config.png
---

Your article content starts here...

Organizing into subsections

Use a forward slash (/) in a section name to place an article inside a child section:

sections: [[Installation/Windows]]

The sync engine walks each path segment in order, finding or creating each term as a child of the previous. This creates a top-level Installation section with a Windows child and files the article there — you can nest as deeply as you like. Plain values without / are assigned at the top level, and multiple sections are supported:

sections: [[Installation/Windows, Troubleshooting]]

Add a featured_image field to set the article’s featured image (post thumbnail). The aliases thumbnail, cover, and image work identically:

---
title: "Advanced Configuration"
featured_image: images/advanced-config.png
---

How it behaves:

  • Paths are resolved like body images. A relative path (including ./ and ../) is resolved against the Markdown file’s location in the repository; an absolute https:// URL is used as-is.
  • Requires Import external media. The image is sideloaded into the Media Library, so the Import external media setting must be enabled. If it is off, the field is ignored.
  • Images are never downloaded twice. If the same image is used in the article body or by another article, the existing Media Library copy is reused.
  • Removing the field clears the thumbnail. If a later sync finds no featured_image in the frontmatter, a featured image that was set by the importer is removed. A featured image you set manually in the WordPress editor is left untouched.

Step 2 — Create a GitHub Personal Access Token

The plugin needs permission to read your repository (and to write, if you enable push-back). Skip this step only if your repo is public and you do not need push-back.

  1. On GitHub, click your profile picture → Settings.
  2. Scroll to Developer settingsPersonal access tokensFine-grained tokens.
  3. Click Generate new token. (Direct link: github.com/settings/personal-access-tokens/newOpens in a new window.)
  4. Give it a name such as “Knowledge Base Sync”.
  5. Set Resource owner to the account or organization that owns your repositories. (For organization repos, the org must also allow fine-grained tokens under Organization Settings → Personal access tokens.)
  6. Under Repository access, choose Only select repositories and pick your docs repo.
  7. Under Permissions → Repository permissions, set Contents to Read-only for import, or Read and write if you plan to push articles back to GitHub.
  8. Click Generate token and copy it immediately — GitHub won’t show it again.

Keep this token safe. You’ll paste it into the plugin on the next step. Because fine-grained tokens are scoped per owner, you can set a per-mapping token later if different repositories belong to different owners.

Step 3 — Connect your repository in plugin settings

All settings live under Knowledge Base → Settings → GitHub.

Global settings

SettingDescription
GitHub Personal Access TokenDefault PAT used when a mapping does not specify its own. Stored encrypted. Use the Verify Token button to confirm it is valid.
Webhook SecretHMAC secret used to validate incoming push webhook payloads. Stored encrypted. This is a string you choose yourself and enter on both sides — see Step 5 for how to generate it and add the matching value on GitHub.
Import external mediaWhen enabled, images in imported Markdown are sideloaded into the Media Library and their URLs rewritten to local copies. Already-sideloaded images are reused on re-import.
Auto-push on saveWhen enabled, a linked article is automatically pushed to GitHub whenever it is saved. Skipped during autosaves, revisions, and webhook-triggered imports to prevent loops. Recommended: leave this off and push manually (see below) — every save otherwise creates a new commit, which can quickly clutter your repository history.

Repository mappings

Each mapping configures one import/export source. Click Add Repository in the Repository Mappings section and fill in:

FieldDescription
ProductThe Knowledge Base product (wzkb_product term) to assign articles to when frontmatter does not specify one.
Personal Access TokenPer-mapping PAT. Overrides the global PAT for this repository — useful when repos belong to different owners. Leave blank to use the global token.
RepositoryBegin typing to search repositories accessible with the configured token, then select owner/repo-name from the dropdown.
Folder PathSubdirectory to restrict imports to, e.g. docs/. Leave blank to import the entire repo. The folder is scanned recursively, so all .md files in its subfolders are included too.
BranchBranch, tag, or SHA to import from. Leave blank to use the repository default branch (wizard) or accept pushes to any branch (webhook).
Article StatusForce all imported articles to this status, overriding any status set in frontmatter. Leave on Use frontmatter to let each article control its own status.
Slug Conflict HandlingWhat to do when a GitHub file’s slug matches an article that was not imported from GitHub: Overwrite the existing article, Skip — leave the existing article unchanged, or Create a new article with a suffixed slug.
When a File is DeletedWhat to do with the linked article when its source file is removed from the repository: Switch to draft or Delete permanently.
Push-backEnable pushing article changes back to GitHub (auto-push and the export wizard). Requires the PAT to have contents: write.
Commit author name / emailOptional. Attribute push commits to this identity. Leave blank to use the PAT owner’s profile.
StatusSet to Disabled to pause a mapping entirely — no imports, no webhook processing, no push-back — without deleting it.

Save your settings when done.

Step 4 — Run your first import

Go to Knowledge Base → GitHub and select the Import tab. Select your mapping (or — All Mappings —), optionally override the branch/ref, optionally force re-import of files whose SHA has not changed, then click Import. The wizard fetches your files one by one and shows a live results table — created, updated, skipped, or any errors.

Once an article is imported, the plugin remembers which file it came from. On future imports, only files that have actually changed are re-processed, so large doc sets import quickly.

Only files ending in .md or .markdown are imported — any other files in the folder (images, LICENSE, .json, etc.) are ignored. The whole repository (or the configured folder) is scanned recursively, so you can organize your docs into nested subfolders however you like.

Step 5 — Set up automatic sync (optional)

Instead of running the import wizard manually every time you push, you can have GitHub automatically notify your site.

  1. In your GitHub repository, go to Settings → Webhooks → Add webhook.
  2. Set Payload URL to https://example.com/wp-json/wzkb/v1/github/webhook (using your own site’s domain).
  3. Set Content type to application/json.
  4. Generate any random string, paste it into Secret, and paste the same string into the Webhook Secret field in Knowledge Base settings.
  5. Under “Which events would you like to trigger this webhook?” Choose “Just the push event“.
  6. Click Add webhook.

From this point on, pushing to your repo triggers an automatic import. The endpoint validates the X-Hub-Signature-256 header before processing, then matches the push to your mappings by repository owner and name. If a mapping specifies a branch, only pushes to that branch are processed; mappings with no branch accept pushes from any branch.

Files added or modified in the push are imported. Renamed files update the stored path and re-import content. Removed files are drafted or deleted according to the mapping’s When a File is Deleted setting. Only the files that changed in the push are processed, so it’s fast.

Pushing changes back to GitHub

If a mapping has Push-back enabled (and its PAT has write access), you can send WordPress-side edits back to the source .md file. The exporter converts the article’s Gutenberg blocks back to Markdown, rebuilds the YAML front matter, and then commits it to GitHub.

There are three ways to push:

  • From the article editor — linked articles show a GitHub meta box with the source file, last-synced time, last-push commit link, and Push to GitHub / Pull from GitHub buttons. Push commits the current article to GitHub; Pull re-imports the live file from GitHub, overwriting the post content. A push is skipped automatically when the generated Markdown is byte-for-byte identical to what GitHub already has.
  • Automatically on save — turn on Auto-push on save to commit every time a linked article is saved (autosaves, revisions, and webhook imports are skipped to avoid loops). The GitHub round-trip is deferred to WP-Cron via the wzkb_github_auto_push_post event, so the editor’s save request never blocks on GitHub latency.
  • In bulk via the export wizard — go to Knowledge Base → GitHub, select the Export tab, choose a push-enabled mapping, click List Articles to see what has changed since the last sync, then click Push to GitHub. The wizard bundles all changed files into a single commit per mapping (using the Git Data API) to keep your history clean and automatically skips unchanged files.

Working with images

Images in your Markdown are imported too. When Import external media is enabled, the plugin downloads each image to your Media Library and rewrites the article to reference the local copy, so your images are self-hosted. Both relative and absolute image sources are handled:

![[Screenshot]](../images/screenshot.png)
![[Logo]](https://example.com/assets/logo.png)
  • Relative paths are resolved against the Markdown file’s location in the repository and fetched from the corresponding raw.githubusercontent.com URL.
  • Absolute URLs (any http(s):// source, including external hosts) are downloaded as-is.
  • Images that already point to your own site are left untouched.

Sideloaded attachments carry a stable dedup key (_wzkb_github_media_source). For GitHub raw URLs, the branch segment is stripped from this key, so the same image resolves to the same URL across branches and is never uploaded twice on re-import.

REST endpoints

Webhook

POST https://example.com/wp-json/wzkb/v1/github/webhook

Receives GitHub push events. Validates the X-Hub-Signature-256 HMAC header against the configured Webhook Secret before processing. Accepts both .md and .markdown files.

On large pushes, the endpoint returns HTTP 202 immediately and processes the files in the background via WP-Cron. Work is split into chunks (default 10 files per tick; see wzkb_github_webhook_chunk_size filter below), and each chunk reschedules the next one automatically. Progress and errors survive across ticks — files that fail due to a transient lock or a network hiccup are retried up to 3 times before being recorded as a permanent failure in the webhook log. GitHub, therefore, never times out waiting for a response, even when a push touches hundreds of files.

Validate

GET https://example.com/wp-json/wzkb/v1/github/validate

Returns the authenticated user’s GitHub login for the configured PAT. Useful for confirming token validity and scope without triggering a full import. (This is what the Verify Token button in settings calls.)

Developer hooks

Filters

wzkb_github_skip_file

Skip a file before it is fetched from GitHub.

add_filter(
    'wzkb_github_skip_file',
    function ( bool $skip, string $file_path, array $mapping ): bool {
        // Skip any file in a _drafts folder.
        return $skip || str_contains( $file_path, '/_drafts/' );
    },
    10,
    3
);
ParameterTypeDescription
$skipboolCurrent skip decision.
$file_pathstringRepo-relative path to the .md file.
$mappingarrayFull repository mapping array.

wzkb_github_pre_import

Modify or cancel an import just before the post is inserted or updated. Return the array with 'skip' => true to abort.

add_filter(
    'wzkb_github_pre_import',
    function ( array $file_data ): array {
        if ( get_option( 'my_import_freeze' ) ) {
            $file_data[['skip']] = true;
        }
        return $file_data;
    }
);

The $file_data array contains: repo_owner, repo_name, file_path, ref, title, slug, status, post_content, frontmatter, mapping, skip.

wzkb_github_markdown_html

Filter the HTML produced by Parsedown before block conversion.

add_filter(
    'wzkb_github_markdown_html',
    function ( string $html, string $markdown_body ): string {
        return str_replace( '{{note}}', '<div class="note">', $html );
    },
    10,
    2
);

wzkb_github_pre_push

Modify or cancel a push just before the article is committed to GitHub. Return the array with 'skip' => true to abort the push for that article.

add_filter(
    'wzkb_github_pre_push',
    function ( array $push_data ): array {
        // Never push articles in a particular category.
        if ( has_term( 'internal', 'wzkb_category', $push_data[['post_id']] ) ) {
            $push_data[['skip']] = true;
        }
        return $push_data;
    }
);

The $push_data array contains: post_id, owner, repo, path, branch, sha, author, skip.

wzkb_github_api_args

Filter the wp_remote_request() arguments for every GitHub API call (headers, timeout, etc.).

add_filter(
    'wzkb_github_api_args',
    function ( array $args, string $url ): array {
        $args[['timeout']] = 30;
        return $args;
    },
    10,
    2
);

wzkb_github_webhook_chunk_size

Number of files processed per WP-Cron tick during webhook background processing. Default: 10. Increase for faster processing on powerful hosts; decrease if individual files are slow (large images, many sideloads).

add_filter( 'wzkb_github_webhook_chunk_size', fn() => 5 );

wzkb_github_max_images_per_file

Maximum number of images sideloaded from a single Markdown file. Default: 25. Images beyond this limit are left with their original URLs.

add_filter( 'wzkb_github_max_images_per_file', fn() => 10 );

wzkb_github_max_image_bytes

Maximum file size (in bytes) for a sideloaded image. Default: 10 * MB_IN_BYTES (10 MB). Images larger than this are skipped and keep their original URL.

// Cap at 5 MB.
add_filter( 'wzkb_github_max_image_bytes', fn() => 5 * MB_IN_BYTES );

wzkb_github_export_batch_size

Number of linked posts fetched per database query during the export wizard’s list phase. Default: 200. Lower this on memory-constrained sites with large KB article counts.

add_filter( 'wzkb_github_export_batch_size', fn() => 100 );

Actions

wzkb_github_post_import

Fires after a KB article has been successfully inserted or updated.

add_action(
    'wzkb_github_post_import',
    function ( int $post_id, array $file_data ): void {
        my_search_index_sync( $post_id );
    },
    10,
    2
);

wzkb_github_post_push

Fires after a KB article has been successfully pushed to GitHub. The second argument is the raw GitHub API response array (single push via Push_Handler/Contents API) or the GitHub commit URL string (export wizard).

add_action(
    'wzkb_github_post_push',
    function ( int $post_id, $context ): void {
        // Log every push.
    },
    10,
    2
);

Post meta schema

The sync engine and push handlers write the following meta keys on every linked article:

Meta keyDescription
_wzkb_github_repoowner/repo string identifying the source repository.
_wzkb_github_pathRepo-relative file path (e.g. docs/setup.md).
_wzkb_github_shaGit blob SHA at the time of last import/push. Used for change detection.
_wzkb_github_last_syncUnix timestamp of the last successful import or push.
_wzkb_github_source_urlgithub.com blob URL for the source file.
_wzkb_github_doc_idValue of the id frontmatter field, if present.
_wzkb_github_last_push_commitURL of the most recent commit created by a push-back.

Sideloaded attachments carry _wzkb_github_media_source to prevent duplicate uploads across re-imports.

Extending the sync engine

Import_Processor is designed for extension. find_github_post() is protected, allowing a subclass to override how existing posts are located (for example, to match on _wzkb_github_doc_id instead of file path).

build_post_map( $owner, $repo ) is public — call it once before a batch and pass the result into process_file() so the batch shares a single DB query instead of doing one per file.

use WebberZoneKnowledge_BaseProGitHubImport_Processor;

class My_Importer extends Import_Processor {

    protected function find_github_post(
        string $repo_owner,
        string $repo_name,
        string $file_path
    ): int {
        // Locate by doc_id frontmatter field instead of file path.
        // ...
        return $post_id;
    }
}

Webhook_Handler::NAMESPACE is protected, so subclasses can override the REST namespace if needed.

Troubleshooting

The file is imported, but the article looks wrong

Check that the frontmatter block opens and closes with --- on its own line with no extra spaces.

The Section wasn’t created

Make sure the section name in the front matter is exactly what you want — the plugin creates it if it doesn’t exist, so a typo can create a duplicate.

Image not appearing?

Confirm Import external media is enabled and the image path in Markdown is correct relative to the file.

Push button missing or push fails?

The article must be linked to GitHub (imported, or with the _wzkb_github_repo/_wzkb_github_path meta set), the mapping must be enabled, and the PAT must have contents: write. Use Verify Token in settings to confirm the scope.

Pull button returns a “locked” error?

Another pull (or a concurrent webhook import) is already in progress for that article. Wait a few seconds and try again; the lock is released when the first operation completes.

Want to temporarily stop a mapping without deleting it?

Set its Status field to Disabled in the repository mapping settings.

Support

For questions about the GitHub integration, open a support ticket.

Was this article helpful?

Leave a Reply

Your email address will not be published. Required fields are marked *