Migration Tools
Quesby includes powerful tools to help you migrate content from other platforms and content management systems. These tools are designed to be standalone, configurable, and easy to use.
3 Steps in 30 Seconds
- Configure - Set up your migration config
- Test - Run dry-run to preview changes
- Import - Execute the migration
# Quick startcd tools/legacy-content-importernpm installcp config.example.yml my-config.ymlnode index.js --config my-config.yml --dry-runnode index.js --config my-config.ymlSupported Platforms: Jekyll, Hugo, Ghost, WordPress (XML export)
Available Tools
Legacy Content Importer
The Legacy Content Importer is a comprehensive tool that converts content from various platforms into Quesby's ULID-based system.
Features:
- Automatic ULID generation for all content
- Flexible frontmatter field mapping
- Slug generation from titles
- SEO-friendly alias creation
- Image path handling and conversion
- Backup functionality before import
- Dry-run mode for safe testing
- Support for multiple content sources
Note: For detailed information about ULID system and content structure, see the Content Management Guide.
Quick Start
1. Install Dependencies
cd tools/legacy-content-importernpm install2. Configure the Tool
Copy the example configuration and customize it for your needs:
cp config.example.yml my-config.yml3. Test the Import
Always test with dry-run mode first:
node index.js --config my-config.yml --dry-run4. Run the Import
When you're satisfied with the test results:
node index.js --config my-config.ymlAdvanced: For npm script integration, see Advanced Usage section.
Supported Platforms
Jekyll
source: "./_posts"target: "./src/content/posts"author: "Your Name"fieldMappings: title: ["title"] description: ["excerpt"] date: "date" tags: "tags" category: "categories"Hugo
source: "./content/posts"target: "./src/content/posts"author: "Your Name"fieldMappings: title: ["title"] description: ["description"] date: "date" tags: "tags" category: "categories"Ghost
source: "./ghost-export/posts"target: "./src/content/posts"author: "Your Name"fieldMappings: title: ["title"] description: ["excerpt"] date: "published_at" tags: "tags"WordPress
Option 1: Using wordpress-export-to-markdown (Recommended)
# First, convert WordPress XML to Markdownnpm install wordpress-export-to-markdownnpx wordpress-export-to-markdown export.xml wordpress-export/Option 2: Using WP Markdown Exporter Plugin Install the "WP Markdown Exporter" plugin in WordPress and export your content.
Configuration:
source: "./wordpress-export"target: "./src/content/posts"author: "Your Name"fieldMappings: title: ["post_title"] description: ["post_excerpt"] date: "post_date" tags: "post_tags" category: "post_category"Configuration Options
Basic Configuration
# Source and target directoriessource: "./old-blog"target: "./src/content/posts"
# Default valuesauthor: "Your Name"imagePath: "/img/"
# Field mappingsfieldMappings: title: ["page-title", "seoTitle", "title"] description: ["descrizione", "description", "excerpt"] date: "date" tags: "tags" category: "category" image: "image" author: "author" draft: "draft" featured: "featured" type: "type"
# Content processingcontent: addCategoryToTags: true createAliases: true aliasPattern: "/blog/{slug}/"
# Backup settingsbackup: enabled: true directory: "backup-before-import"Advanced Configuration
Custom Field Mappings
You can map any legacy field to any Quesby field:
fieldMappings: title: ["post_title", "title", "headline"] description: ["meta_description", "excerpt", "summary"] customField: "legacy_custom_field"Multiple Title Sources
The tool will try each field in order until it finds a value:
fieldMappings: title: ["page-title", "seoTitle", "title", "headline"] description: ["descrizione", "description", "excerpt", "summary"]Custom Alias Patterns
Create custom URL patterns for aliases:
content: aliasPattern: "/old-blog/{slug}/" # Custom pattern # or aliasPattern: "/archive/{year}/{month}/{slug}/" # Date-based patternCommand Line Usage
Basic Commands
# Show helpnode index.js --help
# Dry run with parametersnode index.js --source ./old-blog --target ./src/content/posts --dry-run
# Real import with parametersnode index.js --source ./old-blog --target ./src/content/posts
# Use configuration filenode index.js --config my-config.yml --dry-runnode index.js --config my-config.ymlAdvanced Options
# Custom author and image pathnode index.js \ --source ./old-blog \ --target ./src/content/posts \ --author "Your Name" \ --image-path "/assets/images/" \ --verbose
# Disable backupnode index.js \ --source ./old-blog \ --target ./src/content/posts \ --no-backupBest Practices
1. Always Test First
# Always run dry-run firstnpm run tools:import:config:dry2. Backup Your Data
The tool creates automatic backups, but you should also:
# Create your own backupcp -r src/content/posts src/content/posts-backup3. Review the Results
After import, check:
- All content was imported correctly
- ULIDs were generated properly
- Slugs are URL-friendly
- Images are accessible
- Aliases work for old URLs
4. Test Your Site
# Build and test your sitenpm run buildnpm run dev5. Update Internal Links
After migration, you may need to update internal links in your content to use the new ULID-based URLs.
Troubleshooting
Common Issues
"Source directory not found"
Make sure the source path is correct and accessible:
# Check if directory existsls -la ./old-blog
# Use absolute path if needednode index.js --source "/full/path/to/old-blog" --target ./src/content/posts"No markdown files found"
Ensure your source directory contains .md files:
# Check for markdown filesfind ./old-blog -name "*.md" | head -10"Target directory already exists"
The tool won't overwrite existing content. Either:
- Use a different target directory
- Remove existing content first
- Use the
--forceflag to overwrite existing files
Note: The
--forceflag is implemented and will overwrite existing content files. Use with caution and always backup first.
"Field mapping errors"
Check your configuration file for typos in field names:
# Make sure field names match exactlyfieldMappings: title: ["title"] # Not "Title" or "TITLE"Getting Help
- Check the tool's README:
tools/legacy-content-importer/README.md - Run with
--helpflag:node index.js --help - Use verbose mode:
node index.js --verbose - Check the project's GitHub issues
Advanced Usage
NPM Scripts Integration
For projects that prefer npm scripts, you can add these to your package.json:
{ "scripts": { "tools:import:config": "cd tools/legacy-content-importer && node index.js --config", "tools:import:config:dry": "cd tools/legacy-content-importer && node index.js --config --dry-run" }}Usage:
# Dry runnpm run tools:import:config:dry my-config.yml
# Importnpm run tools:import:config my-config.ymlCustom Field Processors
You can extend the tool with custom field processors:
module.exports = { processCustomField: (value) => { // Your custom processing logic return value.toUpperCase(); }};Contributing
To contribute to migration tools:
- Fork the repository
- Create a feature branch
- Add your tool to the
tools/directory - Follow the tool requirements in
tools/README.md - Submit a pull request
Tool Requirements
When contributing new migration tools:
- Language: JavaScript/Node.js preferred
- Structure: Follow existing tool structure in
tools/legacy-content-importer/ - Documentation: Include README.md with usage examples
- Testing: Add test cases for your tool
- Configuration: Use YAML configuration files
- Error Handling: Implement proper error messages and logging
- CLI: Support standard flags (
--help,--verbose,--dry-run)
Example Tool Structure
tools/my-migration-tool/├── index.js # Main tool file├── config.example.yml # Example configuration├── README.md # Tool documentation├── package.json # Dependencies└── test/ # Test files └── test.jsLicense
Migration tools are licensed under the same MIT license as Quesby.