There comes a point in every team’s journey where you start questioning whether you really need another SaaS subscription. For project management, Asana is excellent – but what if you want full control over your data, integration with your self-hosted tools, and the ability to customize everything?

That’s why I built asana-to-gitea – a production-ready migration tool that transfers your Asana projects into self-hosted Gitea repositories.

Why Migrate from Asana to Gitea?

Before diving into the technical details, let’s talk about why you’d want to do this:

Complete Data Ownership

With Gitea, your project data lives on your infrastructure. No vendor lock-in, no surprise pricing changes, no concerns about a third party accessing your project plans. Everything is under your control.

Developer-Centric Workflow

If your team already uses Git for version control, having your project management in the same ecosystem makes sense. Issues, pull requests, and project boards all in one place.

Cost Efficiency

Gitea is free and open source. For teams on a budget or those who prefer investing in infrastructure over SaaS subscriptions, this is a compelling reason.

Customization Freedom

Need a custom workflow? Want to integrate with internal tools? With Gitea’s API and self-hosted nature, you can build exactly what you need.

The Challenge of Migration

Moving from one project management system to another isn’t trivial. You need to:

  1. Export data from Asana (which provides JSON exports)
  2. Transform that data to match Gitea’s structure
  3. Handle user mapping between systems
  4. Preserve task relationships, assignments, and due dates
  5. Avoid creating duplicates if you run the migration multiple times
  6. Deal with API rate limits

That’s a lot of manual work – or it would be without automation.

What I Built

The asana-to-gitea migrator is a TypeScript CLI tool built with Bun that automates this entire process. Here’s what it does:

Core Migration Features

Task to Issue Conversion: Every Asana task becomes a Gitea issue with:

  • Original task description
  • Assignee (mapped from Asana users to Gitea usernames)
  • Due date
  • Completion status
  • Links back to the original Asana task

Section to Label Mapping: Asana organizes tasks into sections within projects (like “To Do”, “In Progress”, “Done”). Since Gitea doesn’t have the concept of sections, and the API doesn’t yet support automatic project board assignment, the migrator converts each section into a label. This clever workaround serves two purposes: you can filter issues by their original Asana section, and more importantly, you can use these labels to batch-assign issues to project boards efficiently after migration.

Smart Deduplication: The tool tracks which tasks have already been migrated using a state file. If you run it again (maybe you added a few more tasks in Asana), it only creates issues for new tasks. No duplicates, no mess.

User Mapping: Through a usermapping.json file, you define how Asana users (identified by email) map to Gitea usernames. The tool automatically assigns issues to the correct people.

Performance and Reliability

I built this with real-world usage in mind:

Built with Bun: Bun is a blazing-fast JavaScript runtime and toolkit. Compared to Node.js, it’s significantly faster for TypeScript execution, package installation, and testing. The whole migration runs quickly even with large projects.

Rate Limit Handling: Gitea has API rate limits. The migrator respects these with built-in throttling, ensuring your migration doesn’t get blocked halfway through.

Comprehensive Testing: Full test coverage with automated testing, ESLint for code quality, and Prettier for consistent formatting. This isn’t a script thrown together – it’s production-ready code.

TypeScript Strictness: Fully typed with TypeScript 5.9 and strict mode enabled. This catches errors at compile time and makes the code more maintainable.

How It Works

Let me walk you through the migration process:

Step 1: Export from Asana

First, you export your Asana projects as JSON files. Asana makes this easy through their export feature. You get one JSON file per project.

Step 2: Prepare Configuration

Create a .env file with your Gitea credentials:

GITEA_URL=https://gitea.example.com
GITEA_TOKEN=your_api_token_here
GITEA_OWNER=your_organization
GITEA_REPO=your_repository

Optionally, create a usermapping.json to map Asana users to Gitea usernames:

{
  "alice@example.com": "alice_gitea",
  "bob@example.com": "bob_gitea"
}

Step 3: Run the Migration

Place your Asana JSON exports in the exports/ directory and run:

bun start

The migrator then:

  1. Scans all JSON files in exports/
  2. Checks which issues already exist in Gitea
  3. Creates labels for each Asana section
  4. Creates issues for tasks that haven’t been migrated yet
  5. Tracks progress in a state file
  6. Handles errors gracefully with detailed logging

Step 4: Organize in Gitea with Project Boards

Here’s where Gitea’s current API limitation comes in: Gitea 1.24.6 doesn’t have API support for project boards (this is planned for Gitea 1.26.0). The tool can’t automatically create boards or assign issues to them, so you’ll need a quick manual setup.

The Label-Based Workaround:

Since all Asana sections are converted to labels during migration, you can use them for efficient batch organization:

  1. Create Project Board: Navigate to your repository’s Projects tab and create a new board

  2. Set Up Columns: Add columns matching your workflow (e.g., “To Do”, “In Progress”, “Done”)

  3. Batch Assign by Label:

    • Go to the Issues tab
    • Filter by a specific section label (like “In Progress”)
    • Select multiple issues using checkboxes
    • Use bulk actions to assign them to your project board
    • Repeat for each section label
  4. Organize: Drag and drop issues between columns as your workflow evolves

This label-based approach lets you quickly organize hundreds of imported issues without clicking through each one individually. Once set up, your project management is fully self-hosted and you have complete control over the workflow.

The Tech Stack

Let’s talk about the technology choices:

Bun Runtime

I chose Bun over Node.js for several reasons:

  • Speed: Bun is 2-3x faster than Node.js for TypeScript execution
  • Built-in TypeScript: No need for ts-node or compilation steps
  • Native test runner: Built-in testing without Jest or Mocha
  • Modern tooling: Package management, bundling, and transpilation all in one

For a migration tool where you might be processing hundreds or thousands of tasks, this performance boost matters.

TypeScript 5.9

Full type safety ensures the migration logic is correct. With strict mode enabled, the compiler catches potential issues before they cause runtime errors. This is especially important when dealing with data transformation.

Organized Architecture

The codebase is structured for maintainability:

src/
├── api/          # Gitea API client
├── types/        # TypeScript type definitions
├── utils/        # Utility functions
└── index.ts      # Main migration logic

Each piece has a clear responsibility, making it easy to understand, test, and extend.

Real-World Usage

I’ve tested this tool with actual Asana projects containing:

  • Multiple sections and tasks
  • Complex task descriptions with formatting
  • Various assignees and due dates
  • Both completed and incomplete tasks

The migration completes successfully, preserving all metadata and relationships. Issues appear in Gitea exactly as you’d expect, with proper assignments, labels, and due dates.

What About Ongoing Sync?

You might be wondering: “What if I still need to use Asana temporarily during the transition?”

The migration tool handles this gracefully through its state tracking. You can:

  1. Run the initial migration
  2. Continue using Asana for a while
  3. Add new tasks in Asana
  4. Run the migrator again
  5. Only the new tasks get migrated

This allows for a phased transition rather than forcing a hard cutover.

Limitations and Trade-offs

Let’s be honest about what this tool doesn’t do (yet):

No Bi-directional Sync: This is a one-way migration, not a sync tool. Once you migrate, you should transition fully to Gitea.

Manual Project Board Setup: Due to Gitea 1.24.6’s lack of API support for project boards, you need to manually create boards and batch-assign issues using the label-based workflow. The good news is that labels make batch assignment efficient - you can filter by section labels and assign dozens of issues at once. This limitation will be resolved when Gitea 1.26.0 adds project board API support.

Simplified Task Structure: Complex Asana features like subtasks, dependencies, and custom fields aren’t fully preserved. The migration focuses on core task data.

No Comment Migration: Task comments aren’t migrated. If you have critical discussions in Asana comments, you’ll need to manually preserve those.

These are conscious trade-offs to keep the tool focused and maintainable. For most teams transitioning to self-hosted project management, the core migration is what matters most.

Performance Characteristics

Based on real-world testing:

  • Small projects (50 tasks): Migration completes in under 10 seconds
  • Medium projects (200 tasks): Around 30-45 seconds
  • Large projects (500+ tasks): 1-2 minutes depending on API response times

The rate limiting ensures you don’t overwhelm Gitea’s API, but it also means very large projects take a bit longer. This is a reasonable trade-off for reliability.

Development Workflow

If you want to contribute or customize the tool:

# Install dependencies
bun install

# Run tests
bun test

# Lint code
bun run lint

# Format code
bun run format

# Type check
bun run type-check

The development setup is straightforward. Bun handles everything from package management to testing without additional configuration.

Future Improvements

There are a few features I’m considering for future versions:

Comment Migration: When Gitea improves its comment API, migrating task discussions would be valuable.

Subtask Support: Converting Asana subtasks to Gitea issue relationships or checklists.

Custom Field Mapping: Preserving Asana’s custom fields as Gitea labels or metadata.

Project Board Automation: Once Gitea 1.26.0 adds API support, automatic project board creation would eliminate the manual setup step.

Incremental Updates: Detecting changes to existing tasks and updating the corresponding Gitea issues.

If any of these would be valuable for your use case, contributions are welcome!

Similar Projects

This asana-to-gitea tool follows a similar philosophy to my Trilium MCP server project: taking existing systems and making them work better through automation and integration. Both projects focus on:

  • Self-hosted solutions
  • Data ownership
  • Production-ready code quality
  • Developer-centric workflows
  • TypeScript with strict typing
  • Comprehensive testing and production readiness

If you’re interested in AI-powered tools, check out my work on MCP integration for personal knowledge management or Roast My CV, a micro-SaaS that uses N8N workflows and Claude Opus 4.5 to deliver brutally honest CV reviews.

Security Considerations

When migrating project management data, security should be a primary concern. The tool handles sensitive information like task descriptions, user assignments, and organizational structure. If you’re interested in supply chain security and dependency management best practices, read about the CrowdStrike npm supply chain attack which highlights the importance of securing development tooling.

Try It Yourself

The project is open source and available at github.com/lilaflo/asana-to-gitea.

Whether you’re migrating a small team project or a large organizational workflow, the tool is designed to make the transition smooth. The README includes detailed setup instructions and troubleshooting tips.

If you find bugs, have feature requests, or want to contribute improvements, open an issue or pull request on GitHub. I’m particularly interested in hearing about migration experiences with large projects or edge cases I haven’t encountered yet.

Final Thoughts

Migrating project management systems is never fun, but it shouldn’t be painful either. With the right tooling, you can move from SaaS platforms to self-hosted solutions while preserving your team’s workflow and historical data.

The asana-to-gitea migrator makes this transition practical. It’s not just a one-off script – it’s a well-tested, type-safe, production-ready tool that respects your data and makes migration as painless as possible.

If you’re considering moving to self-hosted project management, give it a try. Your future self (and your budget) will thank you.


Resources: