# go-onlyoffice [![Go Reference](https://pkg.go.dev/badge/github.com/eslider/go-onlyoffice.svg)](https://pkg.go.dev/github.com/eslider/go-onlyoffice) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Go](https://img.shields.io/badge/Go-1.22+-00ADD8.svg)](https://go.dev) [![Latest Release](https://img.shields.io/github/v/tag/eSlider/go-onlyoffice?sort=semver&label=release)](https://github.com/eSlider/go-onlyoffice/releases) [![GitHub Stars](https://img.shields.io/github/stars/eSlider/go-onlyoffice?style=social)](https://github.com/eSlider/go-onlyoffice/stargazers) Go client library for the [OnlyOffice](https://www.onlyoffice.com/) Project Management API — manage projects, tasks, subtasks, milestones, and users programmatically. Pairs with [go-gitea-helpers](https://github.com/eSlider/go-gitea-helpers) to bridge developer issue trackers with CRM-grade project management for Gantt charts, resource planning, and executive reporting. ## Architecture ```mermaid graph TB subgraph "Developer Tools" GIT["Gitea / GitHub
Issues, PRs, Milestones"] end subgraph "go-onlyoffice" CL["Client"] AUTH["Auth
Token-based"] PRJ["Projects"] TSK["Tasks & Subtasks"] MS["Milestones"] USR["Users"] end subgraph "OnlyOffice CRM" GANTT["Gantt Charts"] PLAN["Project Planning"] RPT["Reports & Dashboards"] PM["PM Workflow"] end GIT -->|"sync issues"| CL CL --> AUTH AUTH --> PRJ AUTH --> TSK AUTH --> MS AUTH --> USR PRJ --> GANTT TSK --> GANTT MS --> PLAN TSK --> RPT PRJ --> PM ``` ## The Problem: Developers vs. Project Managers ```mermaid graph LR subgraph "Engineering World" DEV["Developers"] GITEA["Gitea / GitHub
Issues & PRs"] CODE["Code Reviews"] end subgraph "Management World" PM["Project Managers"] OO["OnlyOffice CRM
Gantt · Planning · Reports"] EXEC["Executives
Status Reports"] end DEV -->|"create issues"| GITEA GITEA -.->|"❌ invisible"| PM PM -->|"manual copy"| OO OO --> EXEC style GITEA fill:#f96,stroke:#333 style OO fill:#69f,stroke:#333 ``` **Without sync:** Project managers manually copy issue titles, deadlines, and status from Gitea into OnlyOffice. Developers don't update the CRM. Gantt charts rot. Reports lie. **With sync:** Issues flow automatically from Gitea to OnlyOffice with start dates, deadlines, and status. PMs get live Gantt charts. Developers keep working in Git. ```mermaid graph LR subgraph "Engineering World" DEV["Developers"] GITEA["Gitea / GitHub"] end subgraph "Sync Bridge" SYNC["go-onlyoffice
+ go-gitea-helpers"] end subgraph "Management World" OO["OnlyOffice CRM"] GANTT["Gantt Charts ✓"] RPT["Reports ✓"] end DEV -->|"create/close issues"| GITEA GITEA -->|"auto-sync"| SYNC SYNC -->|"create/update tasks"| OO OO --> GANTT OO --> RPT style SYNC fill:#4a4,stroke:#333,color:#fff ``` ## Installation ```bash go get github.com/eslider/go-onlyoffice ``` For Gitea sync (optional): ```bash go get github.com/eslider/go-gitea-helpers ``` ## Quick Start ### Connect and List Projects ```go client := onlyoffice.NewClient(onlyoffice.GetEnvironmentCredentials()) projects, _ := client.GetProjects() for _, p := range projects { fmt.Printf("[%d] %s — %d tasks\n", *p.ID, *p.Title, safeInt(p.TaskCountTotal)) } ``` ### Create a Project with Tasks and Deadlines ```go // Create a project project, _ := client.CreateProject(onlyoffice.NewProjectRequest{ Title: "Q1 2026 Release", Description: "Backend API v2 + mobile app redesign", }) // Create tasks with start/end dates (for Gantt chart) client.CreateProjectTask(onlyoffice.NewProjectTaskRequest{ ProjectId: *project.ID, Title: "Design API schema", Description: "OpenAPI 3.1 spec for all endpoints", StartDate: onlyoffice.Time(time.Now()), Deadline: onlyoffice.Time(time.Now().AddDate(0, 0, 14)), Priority: 1, // High }) client.CreateProjectTask(onlyoffice.NewProjectTaskRequest{ ProjectId: *project.ID, Title: "Implement auth service", Description: "JWT + OAuth2 + refresh tokens", StartDate: onlyoffice.Time(time.Now().AddDate(0, 0, 14)), Deadline: onlyoffice.Time(time.Now().AddDate(0, 1, 0)), }) ``` ### List and Filter Tasks ```go // Get all tasks for a project tasks, _ := client.GetTasks(onlyoffice.NewProjectGetTasksRequest(*project.ID)) for _, t := range tasks { status := "open" if t.Status != nil && *t.Status == onlyoffice.ProjectTaskStatusClosed { status = "closed" } fmt.Printf(" [%s] %s", status, *t.Title) if t.Deadline != nil { fmt.Printf(" (due: %s)", t.Deadline.Format("2006-01-02")) } fmt.Println() } ``` ### Update Task Status and Dates ```go // Close a task and set actual end date client.UpdateProjectTask(onlyoffice.ProjectTaskUpdateRequest{ ID: taskID, Title: "Design API schema", Status: onlyoffice.ProjectTaskStatusClosed, Deadline: &onlyoffice.Time(time.Now()), }) ``` ### Get Milestones and Task Progress ```go milestones, _ := client.GetProjectMilestones(project) for _, ms := range milestones { active := int64(0) closed := int64(0) if ms.ActiveTaskCount != nil { active = *ms.ActiveTaskCount } if ms.ClosedTaskCount != nil { closed = *ms.ClosedTaskCount } total := active + closed fmt.Printf("Milestone: %s — %d/%d tasks done", *ms.Title, closed, total) if ms.Deadline != nil { fmt.Printf(" (deadline: %s)", ms.Deadline.Format("2006-01-02")) } fmt.Println() } ``` --- ## Use Case: Gitea → OnlyOffice Sync The primary use case is **bridging developer workflows with project management**. Developers create issues in Gitea; a sync job automatically mirrors them as OnlyOffice tasks with proper start/end dates, enabling PMs to work with Gantt charts without developers leaving their Git workflow. ### Sync Flow ```mermaid sequenceDiagram participant Dev as Developer participant Gitea participant Sync as Sync Job participant OO as OnlyOffice Dev->>Gitea: Create issue "Add OAuth2" Note over Gitea: issue.Created = Feb 13
issue.Deadline = Mar 1 Sync->>Gitea: GET /repos/{org}/*/issues Gitea-->>Sync: issues list (paginated) Sync->>OO: GET /api/2.0/project/filter.json OO-->>Sync: projects list Sync->>Sync: Match Gitea labels → OO projects alt Issue not yet in OnlyOffice Sync->>OO: POST /api/2.0/project/{id}/task.json Note over OO: Task created:
Start: Feb 13, End: Mar 1
Description includes Gitea URL else Issue already synced Sync->>OO: PUT /api/2.0/project/task/{id}.json Note over OO: Title, status, dates updated end Dev->>Gitea: Close issue "Add OAuth2" Sync->>OO: PUT status → Closed Note over OO: Gantt chart updates automatically ``` ### Sync Example ```go package main import ( "fmt" "log" "os" "strings" gitea "github.com/eslider/go-gitea-helpers" onlyoffice "github.com/eslider/go-onlyoffice" ) func main() { // Connect to both services oo := onlyoffice.NewClient(onlyoffice.GetEnvironmentCredentials()) gc, _ := gitea.NewClient(gitea.GetEnvironmentConfig()) owner := os.Getenv("GITEA_OWNER") // Load all Gitea issues and OnlyOffice projects repos, _ := gc.GetAllReposIssues(owner) projects, _ := oo.GetProjects() for repoName, repo := range repos { // Find matching OnlyOffice project by name project := projects.Get(repoName) if project == nil { fmt.Printf("SKIP %s (no matching OO project)\n", repoName) continue } // Load existing tasks tasks, _ := oo.GetTasks(onlyoffice.NewProjectGetTasksRequest(*project.ID)) for _, issue := range repo.Issues { // Check if issue is already synced (URL in description) existing := findSyncedTask(tasks, issue.HTMLURL) if existing != nil { // Update existing task status := onlyoffice.ProjectTaskStatusOpen if issue.State == "closed" { status = onlyoffice.ProjectTaskStatusClosed } oo.UpdateProjectTask(onlyoffice.ProjectTaskUpdateRequest{ ID: *existing.ID, Title: issue.Title, Status: status, StartDate: timePtr(onlyoffice.Time(issue.Created)), Deadline: deadlineFromIssue(issue), }) fmt.Printf(" UPDATED: %s\n", issue.Title) } else { // Create new task status := onlyoffice.ProjectTaskStatusOpen if issue.State == "closed" { status = onlyoffice.ProjectTaskStatusClosed } oo.CreateProjectTask(onlyoffice.NewProjectTaskRequest{ ProjectId: *project.ID, Title: issue.Title, Description: issue.Body + "\n\nURL:" + issue.HTMLURL, StartDate: onlyoffice.Time(issue.Created), Deadline: *deadlineFromIssue(issue), Status: status, }) fmt.Printf(" CREATED: %s\n", issue.Title) } } } } // findSyncedTask checks task descriptions for the Gitea issue URL. func findSyncedTask(tasks []*onlyoffice.Task, issueURL string) *onlyoffice.Task { for _, t := range tasks { if t.Description != nil && strings.Contains(*t.Description, issueURL) { return t } } return nil } ``` ### What Project Managers Get Once synced, OnlyOffice provides without any developer intervention: | Feature | How It Works | |---|---| | **Gantt Charts** | Tasks have `StartDate` and `Deadline` from Gitea issue created/due dates | | **Status Tracking** | Open/closed status mirrors Gitea issue state in real time | | **Milestone Planning** | Gitea milestones map to OnlyOffice milestones with progress % | | **Resource Allocation** | Task assignees sync so PMs see who's working on what | | **Sprint Reports** | Filter by date range to generate sprint/release reports | | **Cross-Repo View** | All repos' issues appear as tasks in a unified project board | | **Executive Dashboards** | Project progress, overdue tasks, team workload at a glance | ### Recommended Sync Architecture ```mermaid graph TB subgraph "Trigger Options" CRON["Cron Job
every 15 min"] HOOK["Gitea Webhook
on issue events"] CLI["Manual CLI
on-demand"] end subgraph "Sync Engine" S["Sync Job"] MAP["Label → Project
Mapping"] MATCH["URL-based Task
Matching"] DATE["Date Translation
Created → Start
Deadline → End
Closed → Actual End"] end subgraph "Output" OO["OnlyOffice Tasks"] GANTT["Gantt Timeline"] REP["PM Reports"] end CRON --> S HOOK --> S CLI --> S S --> MAP S --> MATCH S --> DATE MAP --> OO MATCH --> OO DATE --> OO OO --> GANTT OO --> REP ``` --- ## Use Case: Task Lifecycle Management ### Creating Tasks with Full Metadata ```go task, _ := client.CreateProjectTask(onlyoffice.NewProjectTaskRequest{ ProjectId: projectID, Title: "Implement payment gateway", Description: "Integrate Stripe API for subscription billing", StartDate: onlyoffice.Time(time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)), Deadline: onlyoffice.Time(time.Date(2026, 3, 15, 0, 0, 0, 0, time.UTC)), Priority: 1, // High MilestoneId: milestoneID, Notify: true, }) ``` ### Tracking Task Progress ```go tasks, _ := client.GetTasks(onlyoffice.NewProjectGetTasksRequest(projectID)) open, closed := 0, 0 var overdue []*onlyoffice.Task for _, t := range tasks { if t.Status != nil && *t.Status == onlyoffice.ProjectTaskStatusClosed { closed++ } else { open++ if t.Deadline != nil && t.Deadline.Before(time.Now()) { overdue = append(overdue, t) } } } fmt.Printf("Progress: %d/%d done (%.0f%%)\n", closed, open+closed, float64(closed)/float64(open+closed)*100) if len(overdue) > 0 { fmt.Printf("⚠ %d overdue tasks:\n", len(overdue)) for _, t := range overdue { fmt.Printf(" - %s (due: %s)\n", *t.Title, t.Deadline.Format("2006-01-02")) } } ``` ### Subtask Management Tasks support subtasks for breaking work into smaller pieces: ```go type Task struct { ID *int `json:"id"` Title *string `json:"title"` StartDate *time.Time `json:"startDate"` Deadline *time.Time `json:"deadline"` Description *string `json:"description"` Priority *int `json:"priority"` // High=1, Normal=0, Low=-1 Status *ProjectTaskStatus `json:"status"` // Open=1, Closed=2 Subtasks []any `json:"subtasks"` MilestoneID *int64 `json:"milestoneId"` Responsibles []*User `json:"responsibles"` // Assigned team members // ... timestamps, permissions } ``` --- ## API Reference ### Client | Function | Description | |---|---| | `NewClient(credentials)` | Create a new API client | | `GetEnvironmentCredentials()` | Load from `ONLYOFFICE_*` env vars | | `Auth(credentials)` | Authenticate and get token | | `Query(request, result)` | Execute raw API request | ### Projects | Method | Description | |---|---| | `GetProjects()` | List all projects | | `CreateProject(req)` | Create a new project | | `UpdateProject(req)` | Update project details | | `DeleteProject(id)` | Delete a project | | `GetProjectMilestones(project)` | Get milestones with task counts | ### Tasks | Method | Description | |---|---| | `GetTasks(req)` | List tasks with filtering | | `CreateProjectTask(req)` | Create task with dates, priority, milestone | | `UpdateProjectTask(req)` | Update title, status, dates, priority | ### Task Fields for Gantt | Field | Type | Purpose | |---|---|---| | `StartDate` | `*time.Time` | Gantt bar start | | `Deadline` | `*time.Time` | Gantt bar end | | `Status` | `ProjectTaskStatus` | Open (1) / Closed (2) | | `Priority` | `*int` | High (1) / Normal (0) / Low (-1) | | `MilestoneID` | `*int64` | Groups tasks under milestones | | `Responsibles` | `[]*User` | Assigned team members | | `Subtasks` | `[]any` | Sub-items within a task | ### Users | Method | Description | |---|---| | `GetUsers()` | List all users with profiles | ### Helper Types | Type | Description | |---|---| | `Projects` | `[]*Project` with `.Get(title)` lookup | | `Time` | `time.Time` wrapper with OnlyOffice JSON format | | `Task.GetGiteaIssueLink()` | Extract Gitea URL from task description | ### Calendar, CRM, Subtasks, File upload (v0.2+) Since v0.2 the library also exposes OnlyOffice Workspace surfaces beyond Projects: Calendar events, CRM (Contacts, Companies, Opportunities, Cases, Tasks, History notes) and opportunity file uploads. These helpers return untyped `map[string]any` for flexibility; callers that need typed structs should use the typed Project/Task API above. ```go client := onlyoffice.NewClient(onlyoffice.GetEnvironmentCredentials()) client.SetDefaults(onlyoffice.GetEnvironmentDefaults()) // optional ctx := context.Background() // Calendar events, _ := client.ListEvents(ctx, "2025-01-01", "2025-12-31") client.AddEvent(ctx, "", "Interview", "2025-06-10T10:00:00Z", "2025-06-10T11:00:00Z", "", false) // CRM deals, total, _ := client.ListOpportunities(ctx, 50, 0) company, _ := client.FindCompany(ctx, "ACME") // Subtasks (form-encoded) client.AddSubtask(ctx, "4242", "Prepare CV") ``` ### oo (bundled command) A ready-to-use [Cobra](https://github.com/spf13/cobra) CLI wrapping the library lives under `cmd/oo`. The command tree is **subject-based**, mirroring the [`tea`](https://gitea.com/gitea/tea) CLI: ```bash go install github.com/eslider/go-onlyoffice/cmd/oo@latest # Global: every list-style command takes -o table|json (default: table) oo whoami oo users list oo calendar events --start 2026-04-24 --end 2026-05-01 oo projects list oo projects get 33 oo tasks list --all --verbose oo tasks subtask add 4242 "Prepare CV" oo persons create --first Jane --last Doe --email jane@example.com oo companies create --name "Acme GmbH" --website https://acme.com oo opportunities list oo opportunities stages oo cases list oo crm-tasks categories oo applications sync --path ./applications/2026 --apply # Project Documents (files module) oo projects files list 33 oo projects files upload 33 ./notes.md oo projects files download 12345 --to ./copy.md oo projects files rename 12345 notes-v2.md oo projects files delete 12345 oo tasks files list 208 oo tasks files upload 208 ./cv.pdf oo tasks files detach 208 12345 ``` | Subject | Verbs | |---|---| | `calendar` | `list`, `events`, `add`, `delete` | | `projects` | `list`, `get`, `milestones`, `create`, `update`, `delete`, **`files`** (`list`, `upload`, `download`, `rename`, `delete`) | | `tasks` | `list`, `get`, `create`, `update`, `delete`, `subtask add`, **`files`** (`list`, `upload`, `detach`) | | `users` | `list`, `self` (alias: `oo whoami`) | | `contacts` | `list`, `get`, `delete`, `info-add` | | `persons` | `list` (filtered), `create`, `delete` | | `companies` | `list` (filtered), `create`, `delete` | | `opportunities` | `list`, `get`, `create`, `delete`, `stages`, `member-add` | | `cases` | `list`, `create`, `delete`, `member-add` | | `crm-tasks` | `list`, `create`, `delete`, `categories` | | `applications` | `sync` | The CLI reads `.env` from CWD (godotenv is a CLI-only concern — the library itself never loads dotfiles). Run `oo --help` or `oo --help` for the full command reference. > **0.5.0 migration note:** the command tree was flattened per-subject. Old > flat names (`oo cal-events`, `oo task-list`, `oo crm-contacts`, > `oo applications-sync`, …) were replaced by subject-based equivalents > (`oo calendar events`, `oo tasks list`, `oo contacts list`, > `oo applications sync`). Flags on leaf commands are unchanged. ## Environment Variables | Variable | Description | |---|---| | `ONLYOFFICE_URL` (or `ONLYOFFICE_HOST`) | OnlyOffice instance URL | | `ONLYOFFICE_USER` (or `ONLYOFFICE_NAME`) | Login email or username | | `ONLYOFFICE_PASS` (or `ONLYOFFICE_PASSWORD`) | Password | | `ONLYOFFICE_CALENDAR_ID` | Default calendar id used when omitted (default `1`) | | `ONLYOFFICE_PROJECT_ID` | Default project id used when omitted (default `33`) | ## Examples | Example | Description | |---|---| | [basic](examples/basic/) | List projects and users | | [calendar](examples/calendar/) | List calendars and events, create a new event | | [crm](examples/crm/) | List contacts and opportunities, add company/deal/history note | | [subtasks](examples/subtasks/) | Create a parent task and attach subtasks | | [`cmd/oo`](cmd/oo/) | Full-featured CLI using all modules | ## Related Libraries | Library | Description | |---|---| | [go-gitea-helpers](https://github.com/eSlider/go-gitea-helpers) | Gitea pagination helpers for issue/repo fetching | | [go-matrix-bot](https://github.com/eSlider/go-matrix-bot) | Matrix bot with OnlyOffice task creation from chat | | [go-trade](https://github.com/eSlider/go-trade) | Unified trade data model across exchanges | ## License [MIT](LICENSE)