tristan / asana
A maintained community drop-in replacement for asana/asana
Fund package maintenance!
Requires
- php: >=8.1
- ext-curl: *
- nyholm/psr7: ^1.8
- php-http/discovery: ^1.19
- php-http/multipart-stream-builder: ^1.4
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.8
- phpunit/phpunit: ^10
- squizlabs/php_codesniffer: ^3.9
Suggests
- guzzlehttp/guzzle: Any PSR-18 client works; Guzzle is the most common choice
Replaces
- asana/asana: *
This package is auto-updated.
Last update: 2026-08-05 21:21:10 UTC
README
The maintained replacement for the official Asana PHP client.
asana/asanano longer installs. Every published version of the official package (v0.1 through v1.0.6) requiresnategood/httpful ~0.2, and every version matching that constraint is covered by security advisory PKSA-4dtf-ym9h-t41j — those releases default TLS certificate verification to off. Composer's default policy refuses to install them, socomposer require asana/asananow fails outright:- asana/asana[v0.1, ..., v1.0.6] require nategood/httpful ~0.2 -> found nategood/httpful[0.2.0, ..., 0.3.2] but these were not loaded, because they are affected by security advisories ("PKSA-4dtf-ym9h-t41j").Asana merged a fix permitting
httpful1.0 in October 2024 but never tagged a release containing it, and discontinued support for the package in January 2025. This fork has removednategood/httpfulentirely — see below.
This fork is a drop-in replacement: same Asana\ namespace, same public API, same endpoint
surface. It adds PHP 8.1+ support, a repaired development toolchain, and CI across PHP
8.1–8.5.
As of this release, the library sends every request through a
PSR-18 HTTP client rather than nategood/httpful.
Any PSR-18 client works: php-http/discovery automatically finds and uses whichever one your
application already has installed (Guzzle, Symfony HttpClient, ...). If none is present, the
library falls back to a small bundled curl-backed client (Asana\Http\CurlHttpClient) so it
still works standalone. See examples/example-psr18-http-client.php for how to choose or
inject a specific client, and examples/example-psr7-messages.php for decorating the PSR-7
request/response objects. The client is exposed as the public $dispatcher->httpClient
property, which tests and applications alike may set directly. An endpoint surface
regenerated from Asana's published OpenAPI specification is still planned for a future
release; this one's endpoint surface remains byte-identical to upstream's.
Installation
composer require tristan/asana
If your project has no PSR-18 client installed already, add one — Guzzle is the most common choice:
composer require guzzlehttp/guzzle
The namespace remains Asana\, so existing code needs no changes.
This package declares replace: {"asana/asana": "*"}, which means it also satisfies
asana/asana requirements from your other dependencies. If a package you depend on requires
asana/asana, Composer resolves it to this fork rather than failing on the advisory:
acme/uses-asana 1.0.0 requires asana/asana (^1.0)
tristan/asana v1.1.0 replaces asana/asana (*)
Migrating an existing project: replace "asana/asana" with "tristan/asana" in your
composer.json and run composer update. No source changes are required.
Test
After running composer install run the tests using:
./vendor/bin/phpunit --configuration tests/phpunit.xml
You can also run the phpcs linter:
./vendor/bin/phpcs
Authentication
Personal Access Token
Create a client using a personal access token:
<?php $client = Asana\Client::accessToken('ASANA_PERSONAL_ACCESS_TOKEN');
OAuth 2
Asana supports OAuth 2. asana handles some of the details of the OAuth flow for you.
Create a client using your OAuth Client ID and secret:
<?php $client = Asana\Client::oauth(array( 'client_id' => 'ASANA_CLIENT_ID', 'client_secret' => 'ASANA_CLIENT_SECRET', 'redirect_uri' => 'https://yourapp.com/auth/asana/callback', ));
Redirect the user to the authorization URL obtained from the client's session object:
<?php $url = $client->dispatcher->authorizationUrl();
authorizationUrl takes an optional state parameter, passed by reference, which will be set to a random number if null, or passed through if not null:
<?php $state = null; $url = $client->dispatcher->authorizationUrl($state); // $state will be a random number
Or:
<?php $state = 'foo'; $url = $client->dispatcher->authorizationUrl($state); // $state will still be foo
When the user is redirected back to your callback, check the state URL parameter matches, then pass the code parameter to obtain a bearer token:
<?php if ($_GET['state'] == $state) { $token = $client->dispatcher->fetchToken($_GET['code']); // ... } else { // error! possible CSRF attack }
For webservers, it is common practice to store the state in a secure-only, http-only cookie so that it will automatically be sent by the browser in the callback.
Note: if you're writing a non-browser-based application (e.x. a command line tool) you can use the special redirect URI urn:ietf:wg:oauth:2.0:oob to prompt the user to copy and paste the code into the application.
Usage
The client's methods are divided into several resources: attachments, events, projects, stories, tags, tasks, teams, users, and workspaces.
Methods that return a single object return that object directly:
<?php $me = $client->users->getUser("me"); echo "Hello " . $me->name; $workspaceGid = $me->workspaces[0]->gid; $project = $client->projects->createProjectForWorkspace($workspaceGid, array('name' => 'new project')); echo "Created project with gid: " . $project->gid;
Methods that return multiple items (e.x. getTasks, getProjects, getPortfolios, etc.) return an items iterator by default. See the "Collections" section
Options
Various options can be set globally on the Client.DEFAULTS object, per-client on client.options, or per-request as additional named arguments. For example:
<?php // global: Asana\Client::$DEFAULTS['page_size'] = 1000; // per-client: $client->options['page_size'] = 1000; // per-request: $client->tasks->getTasks(array('project' => 1234), array('page_size' => 1000));
Available options
base_url(default: "https://app.asana.com/api/1.0"): API endpoint base URL to connect tomax_retries(default: 5): number to times to retry if API rate limit is reached or a server error occures. Rate limit retries delay until the rate limit expires, server errors exponentially backoff starting with a 1 second delay.full_payload(default: false): return the entire JSON response instead of the 'data' propery (default for collection methods andevents.get)fieldsandexpand: array of field names to include in the response, or sub-objects to expand in the response. For examplearray('fields' => array('followers', 'assignee')). See API documentation
Collections (methods returning an array as it's 'data' property):
iterator_type(default: "items"): specifies which type of iterator (or not) to return. Valid values are "items" andnull.item_limit(default: null): limits the total number of items of a collection to return (spanning multiple requests in the case of an iterator).page_size(default: 50): limits the number of items per page to fetch at a time.offset: offset token returned by previous calls to the same method (inresponse->next_page->offset)
Events:
poll_interval(default: 5): polling interval for getting new events viaevents->getNextandevents->getIteratorsync: sync token returned by previous calls toevents->get(inresponse->sync)
Asana Change Warnings
You will receive warning logs if performing requests that may be affected by a deprecation. The warning contains a link that explains the deprecation.
If you receive one of these warnings, you should:
Read about the deprecation. Resolve sections of your code that would be affected by the deprecation. Add the deprecation flag to your "asana-enable" header. You can place it on the client for all requests, or place it on a single request.
$client = Asana\Client::accessToken('ASANA_PERSONAL_ACCESS_TOKEN',
array('headers' => array('asana-disable' => 'string_ids')))
or
$client = Asana\Client::accessToken('ASANA_PERSONAL_ACCESS_TOKEN',
array('headers' => array('asana-enable' => 'string_ids,new_sections')))
If you would rather suppress these warnings, you can set
$client = Asana\Client::accessToken('ASANA_PERSONAL_ACCESS_TOKEN',
array('log_asana_change_warnings' => false))
Collections
Items Iterator
By default, methods that return a collection of objects return an item iterator:
<?php $workspaces = $client->workspaces->getWorkspaces(); foreach ($workspaces as $workspace) { var_dump($workspace); }
Internally the iterator may make multiple HTTP requests, with the number of requested results per page being controlled by the page_size option.
Raw API
You can also use the raw API to fetch a page at a time:
<?php $offset = null; while (true) { $page = $client->workspaces->getWorkspaces(null, array('offset' => $offset, 'iterator_type' => null, 'page_size' => 2)); var_dump($page); if (isset($page->next_page)) { $offset = $page->next_page->offset; } else { break; } }
Contributing
Feel free to fork and submit pull requests for the code! Please follow the existing code as an example of style and make sure that all your code passes lint and tests.
To develop:
git clone git@github.com:tristanisham/php-asana.gitcomposer installphpunit --configuration tests/phpunit.xml
Code generation
The specific Asana resource classes in the Gen folder (Tag, Workspace, Task, etc) are
generated code, hence they shouldn't be modified by hand.
Deployment
Repo Owners Only. Take the following steps to issue a new release of the library.
- Merge in the desired changes into the
masterbranch and commit them. - Clone the repo, work on master.
- Bump the package version in the
VERSIONfile to indicate the semantic version change. - Commit the change.
- Tag the commit with
vplus the same version number you set in the file.git tag v1.2.3 - Push changes to origin, including tags:
git push origin master --tags - Log into packagist.org and click on the update button
The rest is automatically done by Composer / Packagist. Visit the tristan/asana package to verify the package was published.
NOTE: If the package did not update on Packagist, log into Packagist and click on the update button to manually update the package