{"id":34638303,"url":"https://github.com/spinen/laravel-clickup","last_synced_at":"2025-12-24T17:12:28.285Z","repository":{"id":48498531,"uuid":"212924265","full_name":"spinen/laravel-clickup","owner":"spinen","description":"SPINEN's Laravel Package for ClickUp.","archived":false,"fork":false,"pushed_at":"2024-04-08T12:07:32.000Z","size":144,"stargazers_count":28,"open_issues_count":4,"forks_count":15,"subscribers_count":7,"default_branch":"develop","last_synced_at":"2025-10-10T00:53:33.038Z","etag":null,"topics":["clickup","clickup-api","laravel-clickup"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spinen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-05T00:47:07.000Z","updated_at":"2024-09-26T07:59:23.000Z","dependencies_parsed_at":"2023-02-10T13:00:38.902Z","dependency_job_id":null,"html_url":"https://github.com/spinen/laravel-clickup","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/spinen/laravel-clickup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spinen%2Flaravel-clickup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spinen%2Flaravel-clickup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spinen%2Flaravel-clickup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spinen%2Flaravel-clickup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spinen","download_url":"https://codeload.github.com/spinen/laravel-clickup/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spinen%2Flaravel-clickup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28005414,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-24T02:00:07.193Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clickup","clickup-api","laravel-clickup"],"created_at":"2025-12-24T17:11:01.458Z","updated_at":"2025-12-24T17:12:28.273Z","avatar_url":"https://github.com/spinen.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SPINEN's Laravel ClickUp\n\n[![Latest Stable Version](https://poser.pugx.org/spinen/laravel-clickup/v/stable)](https://packagist.org/packages/spinen/laravel-clickup)\n[![Latest Unstable Version](https://poser.pugx.org/spinen/laravel-clickup/v/unstable)](https://packagist.org/packages/spinen/laravel-clickup)\n[![Total Downloads](https://poser.pugx.org/spinen/laravel-clickup/downloads)](https://packagist.org/packages/spinen/laravel-clickup)\n[![License](https://poser.pugx.org/spinen/laravel-clickup/license)](https://packagist.org/packages/spinen/laravel-clickup)\n\nPHP package to interface with [ClickUp](https://clickup.com). We strongly encourage you to review [ClickUp's API docs](https://clickup.com/api) to get a feel for what this package can do, as we are just wrapping their API.\n\nWe solely use [Laravel](https://www.laravel.com) for our applications, so this package is written with Laravel in mind. We have tried to make it work outside of Laravel. If there is a request from the community to split this package into 2 parts, then we will consider doing that work.\n\n## Build Status\n\n| Branch | Status | Coverage | Code Quality |\n| ------ | :----: | :------: | :----------: |\n| Develop | [![Build Status](https://github.com/spinen/laravel-clickup/workflows/CI/badge.svg?branch=develop)](https://github.com/spinen/laravel-clickup/workflows/CI/badge.svg?branch=develop) | [![Code Coverage](https://scrutinizer-ci.com/g/spinen/laravel-clickup/badges/coverage.png?b=develop)](https://scrutinizer-ci.com/g/spinen/laravel-clickup/?branch=develop) | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/spinen/laravel-clickup/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/spinen/laravel-clickup/?branch=develop) |\n| Master | [![Build Status](https://github.com/spinen/laravel-clickup/workflows/CI/badge.svg?branch=master)](https://github.com/spinen/laravel-clickup/workflows/CI/badge.svg?branch=master) | [![Code Coverage](https://scrutinizer-ci.com/g/spinen/laravel-clickup/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/spinen/laravel-clickup/?branch=master) | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/spinen/laravel-clickup/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/spinen/laravel-clickup/?branch=master) |\n\n## Table of Contents\n * [Installation](#installation)\n * [Laravel Setup](#laravel-setup)\n    * [Configuration](#configuration)\n        * [Optional Keys](#optional-keys)\n * [Generic PHP Setup](#generic-php-setup)\n    * [Examples](#examples)\n * [Authentication](#authentication)\n    * [OAuth](#oauth)\n    * [Personal Token](#personal-token)\n * [Usage](#usage)\n    * [Supported Actions](#supported-actions)\n    * [Using the Client](#using-the-client)\n        * [Getting the Client object](#getting-the-client-object)\n        * [Models](#models)\n        * [Relationships](#relationships)\n        * [Advanced filtering using \"where\"](#advanced-filtering-using-where)\n    * [More Examples](#more-examples)\n * [Known Issues](#known-issues)\n\n## Installation\n\nInstall ClickUp PHP Package via Composer:\n\n```bash\n$ composer require spinen/laravel-clickup\n```\n\nThe package uses the [auto registration feature](https://laravel.com/docs/master/packages#package-discovery) of Laravel.\n\n## Laravel Setup\n\n### Configuration\n\n1. You will need to make your `User` object implement includes the `Spinen\\ClickUp\\Concerns\\HasClickUp` trait which will allow it to access the Client as an attribute like this: `$user-\u003eclickup`\n\n    ```php\n    \u003c?php\n\n    namespace App;\n\n    use Illuminate\\Contracts\\Auth\\MustVerifyEmail;\n    use Illuminate\\Foundation\\Auth\\User as Authenticatable;\n    use Illuminate\\Notifications\\Notifiable;\n    use Spinen\\ClickUp\\Concerns\\HasClickUp;\n\n    class User extends Authenticatable\n    {\n        use HasClickUp, Notifiable;\n\n        // ...\n    }\n    ```\n\n2. Add the appropriate values to your ```.env``` file\n\n    #### Optional Keys\n    ```bash\n    CLICKUP_CLIENT_ID=\u003cApplication ID, if using OAuth to generate user tokens\u003e\n    CLICKUP_CLIENT_SECRET=\u003cApplication Secret, if using OAuth to generate user tokens\u003e\n    CLICKUP_OAUTH_URL=\u003curl to clickup OAuth flow, default is v2\u003e\n    CLICKUP_URL=\u003curl to clickup API, default is v2\u003e\n    ```\n\n3. _[Optional]_ Publish config \u0026 migration\n\n    #### Config\n    A configuration file named ```clickup.php``` can be published to ```config/``` by running...\n\n    ```bash\n    php artisan vendor:publish --tag=clickup-config\n    ```\n\n    #### Migration\n    Migrations files can be published by running...\n\n    ```bash\n    php artisan vendor:publish --tag=clickup-migrations\n    ```\n\n    You'll need the migration to set the ClickUp API token on your `User` model.\n\n## Generic PHP Setup\n\n### Examples\n\nTo get a `Spinen\\ClickUp\\Api\\Client` instance...\n\n```bash\n$ psysh\nPsy Shell v0.9.9 (PHP 7.3.11 — cli) by Justin Hileman\n\u003e\u003e\u003e $configs = [\n     \"oauth\" =\u003e [\n       \"id\" =\u003e \"\u003cclient_id\u003e\", // if using OAuth\n       \"secret\" =\u003e \"\u003cclient_secret\u003e\", // If using OAuth\n       \"url\" =\u003e \"https://app.clickup.com/api\",\n     ],\n     \"route\" =\u003e [\n       \"enabled\" =\u003e true,\n       \"middleware\" =\u003e [\n         \"web\",\n       ],\n       \"sso\" =\u003e \"clickup/sso\",\n     ],\n     \"url\" =\u003e \"https://api.clickup.com/api/v2\",\n   ];\n\u003e\u003e\u003e $guzzle = new GuzzleHttp\\Client();\n=\u003e GuzzleHttp\\Client {#2379}\n\u003e\u003e\u003e $clickup = new Spinen\\ClickUp\\Api\\Client($configs, $guzzle) // Optionally, pass the token as 3rd parameter\n=\u003e Spinen\\ClickUp\\Api\\Client {#2363}\n\u003e\u003e\u003e $clickup-\u003esetToken('\u003ca token\u003e') // Skip if passed in via constructor\n=\u003e Spinen\\ClickUp\\Api\\Client {#2363}\n```\n\nThe `$clickup` instance will work exactly like all of the examples below, so if you are not using Laravel, then you can use the package once you bootstrap the client.\n\n\n## Authentication\n\nClickUp has 2 ways to authenticate when making API calls... 1) OAuth token or 2) Personal Token.  Either method uses a token that is saved to the `clickup_token` property on the `User` model.\n\n### OAuth\n\nThere is a middleware named `clickup` that you can apply to any route that verifies that the user has a `clickup_token`, and if the user does not, then it redirects the user to ClickUp's OAuth page with the `client_id` where the user selects the team(s) to link with your application.  Upon selecting the team(s), the user is redirected to `/clickup/sso/\u003cuser_id\u003e?code=\u003cOAuth Code\u003e` where the system converts the `code` to a token \u0026 saves it to the user.  Upon saving the `clickup_token`, the user is redirected to the initial page that was protected by the middleware.\n\n\u003e NOTE: You will need to have the `auth` middleware on the routes as the `User` is needed to see if there is a `clickup_token`.\n\nIf you do not want to use the `clickup` middleware to start the OAuth flow, then you can use the `oauthUri` on the `Client` to generate the link for the user...\n\n```bash\n$ php artisan tinker\nPsy Shell v0.9.9 (PHP 7.3.11 — cli) by Justin Hileman\n\u003e\u003e\u003e $clickup = app(Spinen\\ClickUp\\Api\\Client::class)\n=\u003e Spinen\\ClickUp\\Api\\Client {#3035}\n\u003e\u003e\u003e $clickup-\u003eoauthUri(route('clickup.sso.redirect_url', \u003cuser_id\u003e))\n=\u003e \"https://app.clickup.com/api?client_id=\u003cclient_id\u003e\u0026redirect_uri=https%3A%2F%2F\u003cyour.host\u003e2Fclickup%2Fsso%2F\u003cuser_id\u003e\"\n\u003e\u003e\u003e\n```\n\n\u003e NOTE: At this time, there is not a way to remove a token that has been invalidated, so you will need to delete the `clickup_token` on the user to restart the flow.\n\n### Personal Token\n\nIf you do not want to use the OAuth flow, then you can allow the user to provide you a personal token that you can save on the `User`.\n\n```bash\n$ php artisan tinker\nPsy Shell v0.9.9 (PHP 7.3.11 — cli) by Justin Hileman\n\u003e\u003e\u003e $user = App\\User::find(1)\n=\u003e App\\User {#3040\n     id: 1,\n     first_name: \"Bob\",\n     last_name: \"Tester\",\n     email: \"bob.tester@example.com\",\n     email_verified_at: null,\n     created_at: \"2019-11-15 19:49:01\",\n     updated_at: \"2019-11-15 19:49:01\",\n     logged_in_at: \"2019-11-15 19:49:01\",\n     deleted_at: null,\n   }\n\u003e\u003e\u003e $user-\u003eclickup_token = '\u003cpersonal token\u003e';\n=\u003e \"\u003cpersonal token\u003e\"\n\u003e\u003e\u003e $user-\u003esave()\n=\u003e true\n```\n\n## Usage\n\n### Supported Actions\n\nThe primary class is `Spinen\\ClickUp\\Client`.  It gets constructed with 3 parameters...\n\n* `array $configs` - Configuration properties.  See the `clickup.php` file in the `./config` directory for a documented list of options.\n\n* `Guzzle $guzzle` - Instance of `GuzzleHttp\\Client`\n\n* `Token $token` - _[Optional]_ String of the user's token\n\nOnce you new up a `Client` instance, you have the following methods...\n\n* `delete($path)` - Shortcut to the `request()` method with 'DELETE' as the last parameter\n\n* `get($path)` - Shortcut to the `request()` method with 'GET' as the last parameter\n\n* `oauthRequestTokenUsingCode($code)` - Request a token from the OAuth code\n\n* `oauthUri($url)` - Build the URI to the OAuth page with the redirect_url set to `$url`\n\n* `post($path, array $data)` - Shortcut to the `request()` method with 'POST' as the last parameter\n\n* `put($path, array $data)` - Shortcut to the `request()` method with 'PUT' as the last parameter\n\n* `request($path, $data = [], $method = 'GET')` - Make an [API call to ClickUp](https://clickup.com/api) to `$path` with the `$data` using the JWT for the logged in user.\n\n* `setConfigs(array $configs)` - Allow overriding the `$configs` on the `Client` instance.\n\n* `setToken($token)` - Set the token for the ClickUp API\n\n* `uri($path = null, $url = null)` - Generate a full uri for the path to the ClickUp API.\n\n### Using the Client\n\nThe Client is meant to emulate [Laravel's models with Eloquent](https://laravel.com/docs/master/eloquent#retrieving-models). When working with ClickUp resources, you can access properties and relationships [just like you would in Laravel](https://laravel.com/docs/master/eloquent-relationships#querying-relations).\n\n#### Getting the Client object\n\nBy running the migration included in this package, your `User` class will have a `clickup_token` column on it. When you set the user's token, it is encrypted in your database with [Laravel's encryption methods](https://laravel.com/docs/master/encryption#using-the-encrypter). After setting the ClickUp API token, you can access the Client object through `$user-\u003eclickup`.\n\n```php\n$ php artisan tinker\nPsy Shell v0.9.9 (PHP 7.2.19 — cli) by Justin Hileman\n\u003e\u003e\u003e $user = User::find(1);\n=\u003e App\\User {#3631\n     id: 1,\n     first_name: \"Bob\",\n     last_name: \"Tester\",\n     email: \"bob.tester@example.com\",\n     email_verified_at: null,\n     created_at: \"2019-11-15 19:49:01\",\n     updated_at: \"2019-11-15 19:49:01\",\n     logged_in_at: \"2019-11-15 19:49:01\",\n     deleted_at: null,\n   }\n\u003e\u003e\u003e // NOTE: Must have a clickup_token via one of the 2 ways in the Authentication section\n\u003e\u003e\u003e $user-\u003eclickup;\n=\u003e Spinen\\ClickUp\\Api\\Client {#3635}\n```\n\n#### Models\n\nThe API responses are cast into models with the properties cast into the types as defined in the [ClickUp API documentation](https://clickup.com/api).  You can review the models in the `src/` folder.  There is a property named `casts` on each model that instructs the Client on how to cast the properties from the API response.  If the `casts` property is empty, then the properties are not defined in the API docs, so an array is returned.\n\n```php\n\u003e\u003e\u003e $team = $user-\u003eclickUp()-\u003eteams-\u003efirst();\n=\u003e Spinen\\ClickUp\\Team {#3646\n     +exists: true,\n     +incrementing: false,\n     +parentModel: null,\n     +timestamps: false,\n   }\n\u003e\u003e\u003e $team-\u003etoArray(); // Calling toArray() is allowed just like in Laravel\n=\u003e [\n     \"id\" =\u003e \u003c7 digit ClickUp ID\u003e,\n     \"name\" =\u003e \"SPINEN\",\n     \"color\" =\u003e \"#2980B9\",\n     \"avatar\" =\u003e \u003cURL to avatar\u003e,\n     \"members\" =\u003e [\n       [\n    // Keeps going\n```\n\n#### Relationships\n\nSome of the responses have links to the related resources.  If a property has a relationship, you can call it as a method and the additional calls are automatically made \u0026 returned.  The value is stored in place of the original data, so once it is loaded it is cached.\n\n```php\n$folder = $team-\u003espaces-\u003efirst()-\u003efolders-\u003efirst();\n=\u003e Spinen\\ClickUp\\Folder {#3632\n     +exists: true,\n     +incrementing: false,\n     +parentModel: Spinen\\ClickUp\\Space {#3658\n       +exists: true,\n       +incrementing: false,\n       +parentModel: Spinen\\ClickUp\\Team {#3645\n         +exists: true,\n         +incrementing: false,\n         +parentModel: null,\n         +timestamps: false,\n       },\n       +timestamps: false,\n     },\n     +timestamps: false,\n   }\n\u003e\u003e\u003e $folder-\u003elists-\u003ecount();\n=\u003e 5\n\u003e\u003e\u003e $folder-\u003elists-\u003efirst()-\u003ename;\n=\u003e \"Test Folder\"\n```\n\nYou may also call these relationships as attributes, and the Client will return a `Collection` for you (just like Eloquent).\n\n```php\n\u003e\u003e\u003e $folder-\u003elists;\n=\u003e Spinen\\ClickUp\\Support\\Collection {#3650\n     all: [\n       Spinen\\ClickUp\\TaskList {#3636\n         +exists: true,\n         +incrementing: false,\n         +parentModel: Spinen\\ClickUp\\Space {#3658\n           +exists: true,\n           +incrementing: false,\n           +parentModel: Spinen\\ClickUp\\Team {#3645\n             +exists: true,\n             +incrementing: false,\n             +parentModel: null,\n             +timestamps: false,\n           },\n           +timestamps: false,\n         },\n         +timestamps: false,\n       },\n       // Keeps going\n```\n\n#### Advanced filtering using \"where\"\n\nYou can do advanced filters by using `where` on the models\n\n```php\n\u003e\u003e\u003e $team-\u003etasks()-\u003ewhere('space_ids', ['space_id_1', 'space_id_2'])-\u003ewhere('assignees', ['assignee1', 'assignee2'])-\u003eget()-\u003ecount();\n=\u003e 100\n// If there are more than 100 results, they will be paginated. Pass in another parameter to get another page:\n\u003e\u003e\u003e $team-\u003etasks()-\u003ewhere....-\u003ewhere('page', 2)-\u003eget();\n```\n\n\u003e NOTE: The API has a page size of `100` records, so to get to the next page you use the `where` method... ```-\u003ewhere('page', 3)```\n\n### More Examples\n\n```php\n\u003e\u003e\u003e $team = $user-\u003eclickUp()-\u003eteams-\u003efirst();\n=\u003e Spinen\\ClickUp\\Team {#3646\n     +exists: true,\n     +incrementing: false,\n     +parentModel: null,\n     +timestamps: false,\n   }\n\u003e\u003e\u003e $first_space = $team-\u003espaces-\u003efirst();\n=\u003e Spinen\\ClickUp\\Space {#3695\n     +exists: true,\n     +incrementing: false,\n     +parentModel: Spinen\\ClickUp\\Team {#3646\n       +exists: true,\n       +incrementing: false,\n       +parentModel: null,\n       +timestamps: false,\n     },\n     +timestamps: false,\n   }\n\u003e\u003e\u003e $folder = $first_space-\u003efolders-\u003efirst()-\u003etoArray();\n=\u003e [\n     \"id\" =\u003e \u003c7 digit ClickUp ID\u003e,\n     \"name\" =\u003e \"Test folder\",\n     \"orderindex\" =\u003e 3.0,\n     \"override_statuses\" =\u003e true,\n     \"hidden\" =\u003e false,\n     \"task_count\" =\u003e 79,\n     \"archived\" =\u003e false,\n     \"lists\" =\u003e [\n         // Keeps going\n```\n\n## Known Issues\n\n// TODO: Document known issues as we find them\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspinen%2Flaravel-clickup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspinen%2Flaravel-clickup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspinen%2Flaravel-clickup/lists"}