Guru to Discourse Migration: A Technical Guide
A technical guide to migrating from Guru to Discourse. Covers data mapping, API rate limits, HTML-to-Markdown conversion, and attachment handling.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
Guru to Discourse Migration: A Technical Guide
Migrating from Guru to Discourse is a content-architecture translation, not a file copy. Guru stores knowledge as HTML Cards inside a Collection → Folder → Card hierarchy designed for verified, bite-sized answers. Discourse is an open-source forum built around a Category → Topic → Post model designed for threaded discussion. There is no native Guru-to-Discourse importer — Discourse ships with ~40 community-contributed import scripts for other platforms, but none for Guru. (developer.getguru.com)
Your options are a manual export-and-reformat pipeline, a custom API-driven ETL, or a managed migration service. The right choice depends on your card count, folder nesting depth, attachment volume, and whether you need to preserve verification metadata.
This guide covers the architectural differences, API constraints on both sides, data mapping from Cards to Topics, attachment and link handling, a step-by-step migration process with validation, and what cannot be migrated at all.
Guru vs. Discourse: Architecture Differences That Drive Migration Complexity
Guru is a knowledge-sharing platform built around a three-level hierarchy: Collections contain Folders (up to three nested levels), and Folders contain Cards. Cards are the atomic content unit — each holds HTML-formatted content, metadata (verification status, owner, tags, last verified date), and optional file attachments. Guru is designed for short, verified knowledge articles surfaced via browser extensions and Slack integrations. (help.getguru.com)
Discourse is an open-source discussion platform built on Ruby on Rails and PostgreSQL. Its content model is Categories (with subcategory nesting) containing Topics, and each Topic contains Posts. The first post in a topic is the original content; subsequent posts are replies. Discourse also supports Tags — a flat, flexible labeling system that can be applied across categories.
By default, Discourse's max_category_nesting setting is 2, meaning one level of subcategory under a parent category. It can be pushed to 3, but that is a hidden setting requiring admin or provider involvement on hosted instances. (meta.discourse.org)
Key structural mismatches
| Guru concept | Discourse equivalent | Migration constraint |
|---|---|---|
| Collection | Category | 1:1 mapping works well |
| Folder (level 1) | Subcategory | Direct mapping possible |
| Folder (levels 2–3) | Tags or topic title prefix | Discourse defaults to 1 subcategory level |
| Card | Topic (first post) | Card HTML → Markdown conversion needed |
| Card tags | Tags | Direct mapping; Discourse tags are flat |
| Verification status | Custom field or tag | No native equivalent in Discourse |
| Card owner / verifier | Topic owner (user) | Requires user mapping |
| Collection-level permissions | Category permissions (group-based) | Different permission model |
| Internal card links | Topic URL links | Must be rebuilt post-migration |
| Guru Boards | No equivalent | Cannot be migrated; curated link lists must be rebuilt manually |
| Knowledge Alerts | No equivalent | Cannot be migrated; no Discourse notification analog |
| Browser extension surfacing | No equivalent | Discourse has no browser extension for in-context knowledge |
| Slack card lookup | No equivalent | Requires separate Discourse Slack integration configuration |
The deepest pain point is folder depth. Guru allows three levels of folder nesting inside a collection. If your workspace uses deep folder trees, you need to flatten the hierarchy — typically by mapping the first folder level to subcategories and encoding deeper levels as tags or structured topic titles.
What Cannot Be Migrated
Before investing in migration tooling, understand what Guru features have no Discourse analog. These capabilities are effectively lost in migration unless you build custom replacements.
- Guru Boards: Boards are curated collections of cards assembled for onboarding, training, or project context. Discourse has no board concept. Post-migration, you must reconstruct Boards manually as wiki topics with curated links, or use Discourse's Curated lists plugin if available.
- Knowledge Alerts: Guru's Knowledge Alert feature notifies specified users when cards are created or updated. Discourse has watching/tracking per-topic and per-category, but no equivalent of a cross-collection alert trigger based on knowledge updates. Workflows that depend on Knowledge Alerts must be rebuilt using Discourse webhooks or a third-party automation tool.
- Browser extension: Guru's browser extension surfaces cards contextually inside Salesforce, Zendesk, Gmail, and other web apps. Discourse has no browser extension equivalent. If this feature drives significant Guru usage in your organization, Discourse is a poor functional replacement for those workflows — factor this into your platform decision before committing to the migration.
- Slack card lookup: Guru's Slack integration allows card lookup via slash commands. Discourse's Slack integration supports notifications but not interactive knowledge retrieval. Custom Slack app development is required to replicate this behavior.
- Per-card verification workflow: Guru's structured verification cycle (assigned verifier, verification interval, automated reminders) has no Discourse equivalent. You can preserve the state of verification metadata (see below), but the ongoing workflow must be rebuilt externally — in a spreadsheet, a project management tool, or a custom Discourse plugin.
Document these gaps explicitly in your migration plan. Stakeholders who rely on Boards, Knowledge Alerts, or the browser extension need to know their workflows will change before cutover, not after.
How to Export Data from Guru
Guru provides three extraction paths, each with different trade-offs.
Option 1: Collection Export API (recommended for automation)
Guru's Collection Export API generates a .zip file per collection containing cards as YAML + Markdown/HTML pairs, folder metadata as YAML files, and resources (images, PDFs) in a /resources directory. (developer.getguru.com)
The export structure looks like this:
/
cards/
card1.yaml
card1.md
card2.yaml
card2.html
folders/
folder1.yaml
folder2.yaml
resources/
image1.png
sales-deck.pdf
collection.yaml
The collection.yaml file contains tag and tag category metadata (including structured naming like Category:Tag2). Each card's YAML file holds structured metadata — title, ID, verification state, owner, tags — while the corresponding .md or .html file holds the content body.
Archived cards are excluded. Guru collection exports do not include archived Cards. If archived knowledge matters, restore cards before export or plan a separate archive path. (help.getguru.com)
The Collection Export API is the best starting point for automation because it bundles content, metadata, and attachments in a single download. Trigger it programmatically, then parse the ZIP in your ETL pipeline.
Option 2: Card API for granular extraction
The Guru REST API at https://api.getguru.com/api/v1 supports listing and searching cards programmatically. Authentication uses HTTP Basic Auth with your email and API token.
curl -u "user@company.com:YOUR_API_TOKEN" \
"https://api.getguru.com/api/v1/search/query"The card listing endpoint is paginated with a maximum of 50 results per page. (developer.getguru.com)
Guru's API rate limits are enforced but not publicly documented with specific numeric thresholds. You will receive HTTP 429 responses if you send too many requests. Build retry logic with exponential backoff into your extraction script. In practice, sustained extraction at 1–2 requests per second has proven reliable in migration workloads; bursts above ~3 requests per second consistently trigger 429s. These thresholds are empirically observed, not officially documented, and may vary by plan tier or account age. Always implement a Retry-After-aware backoff loop rather than relying on a static rate.
Guru also supports filtered extraction using the lastModified field, which is useful for delta syncs before cutover — full load first, delta second, freeze last. (developer.getguru.com)
Option 3: Card Manager CSV export (manual, small-scale)
From Guru's web app, you can use the Card Manager to select cards and export them as a CSV file. The CSV includes card content in HTML format. This works for small migrations (under ~100 cards) but doesn't scale and omits folder hierarchy information.
How to Import Data into Discourse
Discourse offers three import paths with different performance characteristics and access requirements.
Path 1: REST API (hosted and self-hosted)
The Discourse REST API lets you create categories, topics, and posts programmatically. Authenticate with an admin API key via the Api-Key and Api-Username headers.
To create a topic — which is how a Guru Card maps into Discourse:
curl -X POST "https://your-discourse.com/posts.json" \
-H "Api-Key: YOUR_ADMIN_API_KEY" \
-H "Api-Username: system" \
-H "Content-Type: application/json" \
-d '{
"title": "Card Title from Guru",
"raw": "Markdown content converted from Guru HTML",
"category": 5,
"tags": ["onboarding", "verified"],
"created_at": "2024-06-15T10:30:00Z"
}'Note: you POST /posts.json with title and category parameters. Discourse automatically creates the Topic and assigns the content as the first post. There is no separate /topics endpoint for creation.
The raw field expects Markdown, not HTML. Convert Guru's HTML card content before importing. See the Discourse Markdown dialect section below for dialect-specific rendering constraints.
Discourse API rate limits are strict and layered. The default admin API limit is 60 requests per minute (DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE). Per-IP limits default to 200 requests per minute and 50 per 10 seconds. On Discourse-hosted plans (starter, pro, business), these limits cannot be adjusted. Self-hosted instances can modify limits in app.yml. For a 1,000-card migration via API at 60 req/min, expect ~17 minutes just for topic creation — before uploads or link rewriting. At 5,000 cards, topic creation alone takes ~83 minutes; with two additional API calls per card for attachment uploads and link rewriting, total API time approaches 4–5 hours for that volume, not counting processing overhead. On hosted plans, request a temporary limit increase from Discourse support before starting large migrations. (meta.discourse.org)
Discourse rate limit behavior under sustained load: When the admin API limit is hit, Discourse returns HTTP 429 with a Retry-After header specifying the wait duration in seconds. The Retry-After value is set reliably for admin API limits. However, per-IP throttling may return 429 without a Retry-After header — implement a fallback wait of 10 seconds when the header is absent. Burst requests that exceed the 50 req/10s per-IP limit are dropped immediately; sustained requests within the per-minute limit are processed normally.
Path 2: Import scripts (self-hosted only)
Discourse ships with ~40 community-contributed import scripts in script/import_scripts/. These Ruby scripts use Discourse's internal ActiveRecord models to insert data, ensuring application-level consistency. There is no official Guru importer, so you would write a custom script extending the base importer class. This path requires direct access to the Discourse Rails environment. (meta.discourse.org)
Path 3: Bulk import (self-hosted, large scale)
For migrations exceeding ~10,000 cards, Discourse's script/bulk_import/ framework bypasses ActiveRecord and writes directly to PostgreSQL using COPY commands. Significantly faster, but it skips application-level validations and needs more maintenance when the Discourse schema changes. You need to ensure data integrity entirely in your transformation layer.
Data Mapping: Guru Cards to Discourse Topics
The core mapping is one Guru Card = one Discourse Topic (specifically, the topic's first post).
Mapping the hierarchy
- Guru Collection → Discourse Category: Create one category per collection. If multiple collections share the same audience and permission model, consider consolidating and preserving the original collection name as a tag or prefix.
- Guru Folder (level 1) → Discourse Subcategory: Map first-level folders to subcategories when the folder is navigational and permission-relevant. If the folder is just a filter, make it a tag instead.
- Guru Folder (levels 2–3) → Discourse Tags: Encode deeper folder names as tags. Use a naming convention like
folder:sales-playbooksto distinguish structural tags from content tags. - Guru Card → Discourse Topic: The card's
preferredPhrasebecomes the topic title. The converted Markdown becomes the first post'srawcontent. - Guru Tags → Discourse Tags: Map directly. Discourse tags are lowercase and hyphenated by default — normalize Guru tags accordingly. Discourse tag groups are a good place to organize taxonomy that mirrors Guru's tag categories.
If you have 5 Guru collections each with 10 first-level folders, you'll need 5 categories and 50 subcategories. Discourse has no hard subcategory limit, but navigability degrades past ~15–20 subcategories per parent. Consolidate low-activity folders into tags.
If you have long Guru cards that function as mini-manuals, keep them as single topics. If you have giant operational runbooks, consider splitting them before import so search and navigation stay usable.
Content conversion: HTML to Markdown
Guru stores card content as HTML. Discourse topics expect Markdown in the raw field (the platform renders it to HTML server-side via a modified CommonMark dialect with custom extensions). Injecting raw, complex HTML from Guru directly into Discourse often breaks the Discourse composer for future edits.
Recommended conversion libraries:
- Python:
markdownifyorhtml2text - Node.js:
turndown - Ruby:
reverse_markdown
import markdownify
def convert_guru_html(html_content):
md = markdownify.markdownify(
html_content,
heading_style="ATX",
bullets="-",
strip=["img"] # Handle images separately
)
return md.strip()Discourse Markdown dialect specifics: Discourse uses a modified CommonMark implementation with several platform-specific constructs. Standard converters like turndown and markdownify produce CommonMark that is broadly compatible, but the following Discourse-specific behaviors cause post-import rendering issues and require explicit handling:
- Oneboxing: Any bare URL on its own line is automatically converted to an onebox (rich embed). If your Guru cards contain raw URLs that should render as plain links, wrap them in angle brackets (
<https://example.com>) or include link text to suppress oneboxing behavior. [details]tag: Discourse supports a custom[details=Summary]... [/details]BBCode-style tag for collapsible content. If Guru cards use<details>/<summary>HTML elements, convert them to the Discourse[details]tag rather than leaving them as HTML — the HTML version renders inconsistently across Discourse versions.- Table formatting: Discourse renders standard pipe-delimited Markdown tables, but requires a header separator row. Tables converted from HTML without a separator row silently break and render as plain text. Validate table output from your converter before bulk import.
- BBCode remnants: Discourse's parser tolerates some BBCode (e.g.,
[b],[i],[url]) from legacy content. If Guru cards contain any BBCode from copy-paste history, strip or convert it explicitly — mixed Markdown and BBCode produces unpredictable rendering. - Inline HTML passthrough: Discourse allows a restricted set of inline HTML tags (
<kbd>,<sub>,<sup>,<s>,<mark>). Complex inline HTML (styled<span>elements,<div>wrappers) is sanitized out. Plan for complete loss of inline CSS styling. - Max post length: Discourse enforces a
max_post_lengthsetting (default 32,000 characters). Guru cards that exceed this limit — uncommon but possible for runbooks — must be split before import.
Observed conversion failure rates: In practice, approximately 5–15% of Guru cards require manual remediation after automated HTML-to-Markdown conversion. The primary causes are complex nested tables (which automated converters mis-render), inline styles that carry semantic meaning (colored text indicating status), and embedded iframes or widgets with no Markdown equivalent. Audit your card inventory before migration: cards with <table> elements containing merged cells or colspan/rowspan attributes are the highest-risk category and should be flagged for manual review.
Watch for these additional conversion edge cases:
- Nested lists: Deeply nested
<ul>/<ol>structures can lose indentation during conversion. - Embedded iframes: Discourse allows iframes only from explicitly allowlisted domains (configured in the
allowed_iframessite setting). - Long card titles: Discourse enforces a
max_topic_title_lengthsetting (default 255 characters). Guru'spreferredPhrasehas no practical length limit. Truncate long titles during transformation.
Verification metadata
Guru's verification workflow — Trusted, Needs Verification, Unverified — along with verificationInterval, verifier, and lastVerified fields has no native Discourse equivalent. (developer.getguru.com) The ongoing verification workflow (assigned reviewer, automated reminders, verification intervals) cannot be replicated in stock Discourse and must be managed externally after migration.
For preserving verification state at import time, your options are:
- Tag-based: Apply tags like
verified,needs-review,unverified. Simplest approach, works on hosted Discourse. LoseslastVerifieddate and verifier identity. - Custom fields: On self-hosted instances, use Discourse's plugin API to add custom topic fields for
verification_status,last_verified_date, andverified_by. Preserves the most fidelity but requires plugin development. - First-post metadata block: Append a structured block to the top of each topic's first post:
> **Status:** Verified | **Last Verified:** 2024-06-15 | **Verified by:** jane@company.comDo not assume editors will recreate Guru verification behavior manually after launch. Whatever strategy you pick, bake it into the migration pipeline. If ongoing verification governance is critical to your organization, evaluate whether Discourse is the right destination platform before committing to this migration.
Handling Attachments and Images
Guru card attachments (images, PDFs, slide decks) are stored on Guru's CDN at content.api.getguru.com. These URLs will break after you decommission your Guru workspace.
Don't skip attachment migration. If you migrate card content but leave image URLs pointing to Guru's CDN, every image will break the moment your Guru subscription ends or the URLs expire. Always re-host attachments on the Discourse instance.
The migration process for attachments:
- Download all files from the
/resourcesdirectory in the collection export ZIP, or fetch them individually via authenticated Guru API requests. Guru images behind authenticated URLs cannot be passed directly to Discourse — you must download them locally first. - Upload each file to Discourse via the upload API:
curl -X POST "https://your-discourse.com/uploads.json" \
-H "Api-Key: YOUR_ADMIN_API_KEY" \
-H "Api-Username: system" \
-F "type=composer" \
-F "file=@/path/to/image.png"- Capture the returned
urlandshort_url(e.g.,upload://aBcDeFg.jpeg). - Rewrite references in the Markdown content to use the Discourse
short_urlbefore creating the topic.
Externally hosted images: If cards reference images hosted outside Guru's CDN — e.g., hotlinked from Google Drive or S3 — those won't appear in the collection export's /resources folder. Scan card HTML for external URLs and handle them separately.
For a detailed guide on managing this process at scale, see How to Migrate Images, Attachments & Embeds Without Broken Links.
Rewriting Internal Card Links
Guru cards frequently link to other cards using internal URLs (e.g., https://app.getguru.com/card/...). After migration, these links point nowhere.
Because Discourse assigns new IDs to Topics upon creation, you cannot know the final URL of a linked topic until it has been migrated. The fix is a two-pass approach:
- During topic creation, build a lookup table mapping each Guru card ID to the new Discourse topic ID and slug.
- After all topics are created, run a second pass over every topic's content.
- Use regex to find Guru card URLs and replace them with Discourse topic URLs.
import re
def rewrite_guru_links(markdown, card_id_to_topic_url):
pattern = r'https://app\.getguru\.com/card/([a-zA-Z0-9-]+)'
def replacer(match):
card_id = match.group(1)
return card_id_to_topic_url.get(card_id, match.group(0))
return re.sub(pattern, replacer, markdown)- Update each Discourse topic using
PUT /posts/{id}.jsonwith the rewritten content.
Budget a separate API call per topic for this update pass. Internal links are the most common failure point in knowledge base migrations — don't skip this step.
User Mapping and Permissions
User mapping
Guru card owners and verifiers are identified by email. Discourse users are identified by username. Before migration:
- Export your Guru member list via the API (
GET /api/v1/members). - Compare against your Discourse user base (
GET /admin/users/list/active.json). - Build an email-to-username lookup table.
- For users who don't exist in Discourse, decide whether to provision them or attribute content to a generic "System" account.
- Use the
created_atparameter andApi-Usernameheader when creating topics to preserve authorship and timestamps.
Discourse Trust Levels: Discourse employs a Trust Level system (TL0 to TL4) that controls what users can do — posting images, adding links, etc. If your migration script attributes posts to newly created users at TL0, the API may reject posts containing multiple links or images. Temporarily elevate the Trust Level of migration target users to TL4 during import.
To create a topic "as" a specific user via the Discourse API, set the Api-Username header to that user's username. The API key must have admin-level permissions to impersonate users.
Permission mapping
Guru controls access at the Collection level via Groups, with optional folder-level Viewer permissions. Discourse controls access at the Category level via Groups with See, Reply, and Create granularity. (help.getguru.com)
Key mapping rules:
- Guru Group → Discourse Group: Create a matching Discourse group for each Guru group.
- Collection access → Category permissions: Assign group-based permissions to each Discourse category.
- In Discourse, any group allowed to access a subcategory must also be allowed to access its parent category. This constraint is the biggest reason a literal hierarchy copy often fails for workspaces with complex, mixed-visibility folder structures.
- Guru has no card-level permissions — access is collection-wide. Discourse similarly controls permissions at the category level, so this maps cleanly for most setups.
- If you were using Guru's "Company-wide" share status on specific cards, those map to a public Discourse category.
Step-by-Step Migration Process
Step 1: Audit and plan
- Count total cards, collections, and folders in Guru.
- Identify folder nesting depth — any folders 2+ levels deep need special handling.
- Inventory attachments and internal card links.
- Flag archived cards and decide whether to restore them before export.
- Inventory Boards and Knowledge Alerts — document these separately as they require manual reconstruction post-migration.
- Decide on verification metadata strategy (tags vs. custom fields vs. metadata blocks).
- Map Guru collections to Discourse categories; plan subcategory and tag structure.
- Flag cards with complex HTML tables (colspan/rowspan) for manual remediation.
Step 2: Export from Guru
- Use the Collection Export API to download ZIP files for each collection.
- Alternatively, use the card API for full card objects with metadata.
- Download all attachments referenced in card content.
Start with a pilot. Export a single representative collection first. Inspect cards/, folders/, resources/, and collection.yaml. Confirm which cards are Markdown vs. HTML, and check how internal links are expressed. This pilot tells you whether you need a sanitizer, a full HTML-to-Markdown pass, or just link rewriting. Measure conversion failure rate on the pilot collection before scaling — if more than 20% of cards need manual remediation, revise your transformation pipeline before full extraction.
Step 3: Transform content
- Parse card YAML/HTML from the export.
- Convert HTML content to Markdown.
- Handle Discourse-specific dialect requirements: suppress oneboxing on bare URLs, convert
<details>to[details]tags, validate table header separators. - Normalize tags (lowercase, hyphenated).
- Truncate long titles that exceed Discourse's
max_topic_title_length(default 255 characters). - Check card content length against
max_post_length(default 32,000 characters); split oversized cards. - Build the card-ID mapping table for link rewriting later.
- Generate Discourse-ready payloads: title, raw (Markdown), category ID, tags,
created_at, target username.
Step 4: Create Discourse structure
- Create categories and subcategories via the Discourse API (
POST /categories.json). - Create groups and assign category permissions.
- Pre-create or invite users.
- Store returned category IDs in a mapping dictionary.
Step 5: Upload attachments
- Upload each file via Discourse's
/uploads.jsonendpoint. - Record the mapping of each original Guru attachment URL to the Discourse upload URL.
- Rewrite all attachment references in your transformed content before creating topics.
Step 6: Import topics
- POST each card as a new topic via the Discourse API.
- Throttle to ~1 request/second for writes on hosted instances. When a 429 is returned, read the
Retry-Afterheader; if absent, wait 10 seconds before retrying. - Log the mapping of Guru card ID → Discourse topic ID.
- Expect 5–15% of topics to require manual follow-up based on content complexity.
Step 7: Rewrite internal links
- Run a second pass over all migrated topics.
- Replace Guru internal card links with Discourse topic URLs using your mapping table.
- Replace any remaining Guru attachment URLs with Discourse upload URLs.
- PUT the updated
rawcontent back to each topic.
Step 8: Delta sync before cutover
If the migration spans multiple days, use Guru's lastModified field to capture changes made after the initial export. Run a targeted update of modified cards before cutover. This gives you a much cleaner launch window than a hard freeze on day one. (developer.getguru.com)
For the full operational cutover plan, see The Ultimate Knowledge Base Migration Checklist.
Step 9: Rollback planning
Before cutover, establish your rollback threshold and process:
- Rollback trigger: Define the failure conditions that would trigger a rollback — e.g., more than 10% of topics failing validation, critical internal links unresolvable, or category permissions verified as incorrect.
- Rollback window: Maintain Guru in read-only mode (rather than decommissioning) for at least 2–4 weeks post-cutover. Discourse topic creation is not easily reversible at scale — there is no bulk delete API for topics, and mass deletion via the admin UI is slow. Keeping Guru available as a fallback reference is significantly cheaper than attempting a Discourse rollback.
- Rollback execution: If rollback is required within the cutover window, re-enable Guru access for all users immediately. To clean up Discourse, use the Rails console (
Topic.where("created_at > ?", migration_start_time).destroy_all) on self-hosted instances, or contact Discourse support for hosted plan bulk deletions. - Partial rollback: If only a subset of collections failed migration, consider a hybrid state — failed collections remain in Guru, successful ones move to Discourse — only if you can clearly communicate the split to users without confusion.
Step 10: Validate
- Spot-check 5–10% of migrated topics for content accuracy.
- Verify all images render correctly.
- Test internal links between topics.
- Confirm category/subcategory structure matches your plan.
- Validate that verification tags or metadata blocks are present.
- Test category permissions from a non-admin account.
- Search for any remaining
app.getguru.comorcontent.api.getguru.comURLs — any hits indicate missed link rewrites or failed asset downloads.
Common Failure Modes
HTML conversion is lossy for complex cards. Cards with complex tables (merged cells, colspan/rowspan), colored text carrying semantic meaning, multi-column layouts, or embedded widgets will lose fidelity in the HTML-to-Markdown conversion. Expect 5–15% of cards to require manual remediation; audit HTML table complexity early to set accurate expectations.
Discourse Markdown dialect mismatches. Standard converters produce CommonMark; Discourse's parser has divergences. Bare URLs onebox unintentionally, <details> HTML renders inconsistently, tables without header separators silently break. Test converted output in a staging Discourse instance before bulk import.
Rate limit walls on Discourse-hosted instances. If you're on Discourse's hosted plans (not Enterprise), you cannot increase the 60 req/min admin API limit without requesting a temporary increase from Discourse support. For large migrations (500+ cards), the import step alone takes significant time before uploads and link rewriting.
Guru's undocumented rate limits. Because Guru doesn't publish specific rate limit numbers, your extraction script may suddenly start receiving 429s. Sustained extraction at 1–2 req/sec is reliable; bursts above ~3 req/sec consistently trigger throttling. Always implement exponential backoff.
Orphaned attachments. If cards reference images hosted externally (not on Guru's CDN) — e.g., hotlinked from Google Drive or S3 — those won't appear in the collection export's /resources folder. Scan card HTML for external URLs and handle them separately.
Category navigability. If your migration produces more than ~15–20 subcategories per parent category, Discourse's UI becomes unwieldy. Consolidate low-traffic folders into tags rather than forcing deep subcategory trees.
Doc category index ceiling. If you use Discourse's Doc Categories plugin, the max number of items in an index topic is tied to the Max oneboxes per post setting, which defaults to 50. Large knowledge bases need either segmented indexes or a higher setting. Note that the Doc Categories plugin is under active development — verify compatibility with your Discourse version and any installed theme components in a staging environment before committing to it as a navigation layer. (meta.discourse.org)
No rollback path after decommissioning Guru. If you cancel your Guru subscription before validating the migration, recovery becomes extremely difficult. Keep Guru active in read-only mode for at least 2–4 weeks post-cutover.
Making Discourse Work as a Knowledge Base
Discourse can function as a docs system, but only if you configure it intentionally. The official Doc Categories plugin adds an index topic, a docs-specific sidebar, and in:docs search filtering. (meta.discourse.org)
A useful post-migration configuration pattern:
- Give most users See access, not Create, for docs categories.
- Keep a smaller editor group that can update wiki posts.
- Make migrated topic first-posts into wiki posts so editors can update content without creating new replies.
- Use tags for product, team, or lifecycle metadata.
- Teach users Discourse's search syntax early:
in:docs, category filters, and tag filters.
The Doc Categories plugin is still in active development with known interactions affecting certain theme components and search indexing behavior. Known limitations include the 50-item index ceiling (tied to Max oneboxes per post), incomplete support for nested category display in some themes, and search filter behavior that varies by Discourse version. Test all plugin interactions in a staging instance before committing to a specific UX, and pin your Discourse version until you have validated the setup. (meta.discourse.org)
Discourse's own developer docs repository mirrors Markdown files into a Discourse doc category, uploads images automatically, and uses topics as the durable documentation unit — a strong signal that a Markdown-first Guru migration is the right target shape. (github.com)
When to DIY vs. When to Get Help
DIY is reasonable when:
- You have fewer than 200 cards
- Folder nesting is 1 level deep or flat
- Limited internal card-to-card linking
- Minimal attachments
- No complex HTML tables or embedded media
- No Boards or Knowledge Alerts that need reconstruction planning
- Your team has Python or Ruby scripting capacity
A managed migration makes sense when:
- 500+ cards across multiple collections
- Deep folder nesting requires careful hierarchy flattening
- Hundreds of internal card links need rewriting
- High card complexity (nested tables, inline styles, iframes) pushes manual remediation above ~15% of content
- Verification metadata must be preserved accurately
- Boards and Knowledge Alerts require documented replacement workflows
- You're on Discourse-hosted and can't adjust API rate limits
- You can't dedicate engineering time for 2–3 weeks
A well-executed migration for a mid-size team (500–1,500 cards) typically takes 1–2 weeks end-to-end: audit and planning, script development, at least one full dry run against a staging instance, production migration, and final validation. For 1,500–5,000 cards with complex content, budget 3–4 weeks to account for higher manual remediation volume and extended delta sync windows. Always run a dry run before touching production — every migration has edge cases (unusual formatting, broken links, duplicate titles) that you want to catch before go-live.
At ClonePartner, we've handled knowledge base migrations across platforms with similar architectural mismatches — including Guru to Notion, Slab to Guru, and SharePoint to Guru — and built the tooling to handle HTML conversion, link rewriting, and attachment migration at scale.
Frequently Asked Questions
- Is there a native Guru to Discourse importer?
- No. Discourse ships with ~40 import scripts for other platforms, but there is no official Guru importer. You need to build a custom ETL pipeline using Guru's Collection Export API and Discourse's REST API, or use a managed migration service.
- How long does a Guru to Discourse migration take?
- For a mid-size team with 500–1,500 cards, expect 1–2 weeks end-to-end including audit, script development, dry runs, and production migration. Smaller workspaces under 200 cards can be done in a few days with basic scripting.
- How do I handle Guru's three-level folder structure in Discourse?
- Discourse defaults to one level of subcategory nesting. Map Guru Collections to Categories, first-level Folders to Subcategories, and encode deeper nested folders as Discourse Tags using a naming convention like folder:name.
- What happens to Guru images and attachments after migration?
- Guru-hosted images and files will break when your subscription ends. You must download all attachments and re-upload them to Discourse via its /uploads.json endpoint, then rewrite the URLs in your migrated topic content.
- Can I migrate Guru verification status to Discourse?
- Not natively. Discourse has no built-in verification workflow. You can approximate it using tags (e.g., verified, needs-review), custom topic fields via plugins on self-hosted instances, or structured metadata blocks in the topic body.