Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Clickup Laravel Package

spinen/laravel-clickup

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and pin. composer require spinen/laravel-clickup — but check the composer.json constraints first. The last release was 2021‑05‑25 and it sits on top of spinen/laravel-integration, so on Laravel 9+ you will likely need --ignore-platform-reqs or a fork. Verify before you build anything on it.
  2. Publish the config. php artisan vendor:publish --provider="Spinen\ClickUp\ClickUpServiceProvider" (or --tag=config). You get config/clickup.php; the one value that matters is the API token. Put it in .env as CLICKUP_API_TOKEN and never commit it — ClickUp personal tokens grant full workspace access.
  3. Read the source, not the README. vendor/spinen/laravel-clickup/src/ is small. Look at the Facades/ directory for the entry point (usually Spinen\ClickUp\Facades\ClickUp), then the resource/model classes to see which endpoints are actually wrapped. Cross-reference with spinen/laravel-integration — that base package owns the fluent get()/create()/update()/delete() mechanics and the HTTP client, so most "how do I call X" questions are answered there.
  4. First use case: read-only task pull. The highest-value, lowest-risk first integration is a scheduled job that pulls tasks from a known List ID into your own database (a dashboard, an SLA report, a Slack digest). Start there rather than with writes — you learn the auth path, the pagination shape, and the response hydration without risking mutation of a customer's workspace.
use Spinen\ClickUp\Facades\ClickUp;

$tasks = ClickUp::tasks()->where('list_id', $listId)->get();

Treat that snippet as a shape to verify, not gospel: the exact method names and where list_id belongs (query param vs. path segment) differ across versions of the base package. dd() the result on day one.

Implementation Patterns

Wrap the facade, never call it from controllers. The facade is a static-ish service locator; sprinkle it through your app and you lose testability and the ability to swap in a fake. Put it behind one class:

final class ClickUpClient
{
    public function tasksForList(string $listId): Collection
    {
        return ClickUp::tasks()
            ->where('list_id', $listId)
            ->where('archived', false)
            ->get();
    }
}

Everything downstream depends on ClickUpClient, so you can bind a stub in tests and in local dev.

Sync into Eloquent, then read from Eloquent. ClickUp is slow (network + rate limits) and is not your query engine. Pull on a schedule, upsert into a clickup_tasks table keyed by ClickUp's task id, store the raw payload in a json column. Your UI then joins, filters, and paginates locally. This also gives you a replayable history when ClickUp's API shape shifts under a stale package.

Queue every write and every bulk read. A SyncListTasks job per list, SyncWorkspace as a batch of those. Use ->withoutOverlapping() on the scheduled command and a per-token rate limiter so a large workspace doesn't trip ClickUp's 100 requests/minute/token ceiling:

RateLimiter::for('clickup', fn () => Limit::perMinute(90));

Webhooks are the real-time path — but verify them yourself. Register the webhook via the API (or the ClickUp UI), point it at a route, and compute the HMAC SHA256 of the raw request body using the webhook secret to check the signature field. Do not trust $request->all() for hashing — $request->getContent() only. Then dispatch a job that re-fetches the task by ID; the webhook payload is a hint, not a source of truth.

Resolve IDs out of band. Lists, folders, spaces, and custom field IDs are opaque. Keep a config/clickup.php section (or a small clickup_resources table) mapping human names to IDs so nobody is pasting raw IDs into code.

Testing. Because the package sits on Guzzle via laravel-integration, Http::fake() may not intercept it. Use Guzzle's MockHandler/HandlerStack if the client is injectable, otherwise bind a fake ClickUpClient in the container and reserve one narrow integration test against a sandbox workspace for the HTTP layer.

Gotchas and Tips

  • Dormancy is the headline risk. Four years without a release means ClickUp API v2 changes, new required params, and PHP/Laravel version constraints have all moved on. Budget a day to fork it ("repositories": [{"type": "vcs", ...}]) and patch the client yourself. The package is thin — forking is cheap.
  • Version constraint hell. If composer require fails on Laravel 9/10/11, it's spinen/laravel-integration holding it back, not the ClickUp code. Check both before concluding it's unmaintainable.
  • Personal token vs. OAuth. The config assumes a single token for the whole app. The moment you go multi-tenant you need per-workspace OAuth tokens, and the package has no story for that. Plan to build token resolution yourself — likely a per-tenant client instance rather than the global facade.
  • config:cache will bite you. Any token read at service-provider boot time is frozen into the cached config. Read config('clickup.*') lazily inside the client, not in the provider's register().
  • Pagination is silent. ClickUp list endpoints return a bounded page by default; if your sync "missing" tasks, you're not paginating. Always loop until an empty/short page, and assert row counts against the ClickUp UI once.
  • Custom fields are a nested array, not columns. They arrive as [{id, name, type, value}] with types that vary wildly (labels, dropdowns, currency, relationships). Normalize into a JSON column plus a few extracted keys you actually filter on — do not try to map every type eagerly.
  • Subtasks and archived items are opt-in. subtasks, include_closed, and archived are flags people forget, then file "missing task" bugs. Make them explicit arguments on your client methods rather than implicit defaults.
  • Debug by dumping raw responses. When a hydration error appears, log the JSON before it hits the model layer. Errors from this stack tend to surface as generic exception messages with the useful detail hidden one layer down.
  • Extension point: new endpoints are added by writing your own resource class (or a plain Guzzle call in ClickUpClient) — don't wait for upstream. Anything the package doesn't wrap (Goals, Views, Time Tracking v2) is a five-line method on your own client, and keeping it there also insulates you when you eventually drop the dependency.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings