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

Basecamp Php Laravel Package

netvlies/basecamp-php

A PHP client for the Basecamp API that lets you authenticate and work with Basecamp projects, to-dos, messages, files, and people from Laravel or plain PHP. It provides a fluent, framework-friendly wrapper for common Basecamp endpoints.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin PHP wrapper around the Basecamp 3 (bc3) REST API, built on League OAuth2 for auth and on HTTPlug/PSR-7 for transport. It is not Laravel-aware — there is no service provider, no facade, no config file. You will wire it up yourself.

Install:

composer require netvlies/basecamp-php guzzlehttp/guzzle

You need an app registered at launchpad.37signals.com to get a client ID / secret / redirect URI. Basecamp 3 uses the OAuth2 Authorization Code Grant, so the flow is:

  1. Build a Netvlies\Basecamp\BasecampClient (it extends League\OAuth2\Client\Provider\AbstractProvider) with your credentials and a userAgent. Basecamp rejects requests without a meaningful User-Agent.
  2. getAuthorizationUrl() → redirect the user to Basecamp.
  3. On callback, getAccessToken('authorization_code', ['code' => ...]).
  4. Persist the token (access + refresh + expiry).
  5. Call $provider->authenticate($accessToken) to get an API client with resource accessors (projects(), todos(), todolists(), people(), comments(), etc.).

Minimal first use case — post a todo:

$client   = $provider->authenticate($storedAccessToken);
$projects = $client->projects()->all();
$todo     = $client->todos()->create($todolistId, [
    'content'   => 'Ship the report',
    'due_on'    => '2024-12-01',
    'assignee_ids' => [$userId],
]);

Where to look first: the Client class (resource accessor list), the Resources\* folders (each has all / show / create / update / delete), and the BasecampClient OAuth methods. The README is close to code comments — the source is the real documentation.

Implementation Patterns

Bind a token-scoped client in the container. Because the client is per-user, resolve it on demand rather than as a singleton you configure once:

// AppServiceProvider::register()
$this->app->bind(BasecampClient::class, fn () => new BasecampClient([
    'clientId'     => config('services.basecamp.client_id'),
    'clientSecret' => config('services.basecamp.client_secret'),
    'redirectUri'  => config('services.basecamp.redirect_uri'),
    'userAgent'    => config('services.basecamp.user_agent'),
]));

$this->app->bind(ApiClient::class, function ($app) {
    $token = $app['request']->user()->basecamp_token;
    return $app->make(BasecampClient::class)->authenticate($token);
});

Store tokens on the users table with encrypted casts — Basecamp refresh tokens are long-lived and sensitive.

Wrap resource calls in your own repository. The package's resources are inconsistent in argument order (some take $projectId first, some don't) and expose no pagination helpers. A BasecampRepository class that standardizes getProjects(), getTodos($projectId) etc. will save you from leaking that API into controllers.

Refresh tokens before they expire — authenticate() doesn't do this for you. Either schedule an Artisan command (basecamp:refresh-tokens) that iterates users and calls $provider->getAccessToken('refresh_token', ['refresh_token' => $rt]), or refresh lazily in a middleware when expires < now + 60.

Queue anything that touches multiple projects. Basecamp's API rate-limits per-token and per-IP. A SyncBasecampProjects job with $tries = 3 and backoff() is the safest place to put fan-out loops.

Reuse the same Guzzle client. HTTPlug does auto-discovery, but in Laravel you can hand the package your already-configured Illuminate\Http\Client underlying Guzzle instance via php-http/guzzle7-adapter, avoiding a second HTTP stack.

Gotchas and Tips

  • No pagination. ->all() returns the first page (Basecamp caps at 15–500). You must read Link: <...>; rel="next" yourself — the package doesn't surface it. Wrap calls that can exceed a page in a loop that inspects $response->getHeader('Link') if you're using the raw response.
  • Error handling. Exceptions bubble up from HTTPlug — they're Guzzle exceptions (RequestException, ClientException), not package-specific ones. Catch by status code: 429 = rate limited (sleep Retry-After), 404 = wrong bucket ID, 401 = token expired (refresh then retry).
  • No retries. The wrapper is a single call per method. Add Guzzle middleware for Retry-After and 5xx backoff; Basecamp is flaky under load.
  • Low maintenance signal. 37 stars and a stale feature set means newer endpoints (timesheets, webhooks, card tables, some comment types) may be missing. Check the Resources directory against the bc3 API docs before you commit — you may need to drop down to raw $client->get('...') / $client->post('...') and parse manually.
  • userAgent is required by Basecamp, not just recommended. "MyApp ([email protected])" is the expected format; a bare string like "MyApp" gets rejected or flagged.
  • Bucket/project IDs are integers, but Basecamp serializes them as such — be careful with array casts and strict_types. Some users lose IDs because intval was never applied and in_array fails against JSON.
  • authenticate() returns a fresh client — it doesn't mutate the provider. Don't store the provider as a request-scoped singleton if multiple users are involved in one queue worker.
  • No native testing helpers. Mock at the HTTPlug adapter level (MockClient) rather than trying to fake the Client — that's the only seam the package exposes.
  • Extension point: every resource class is plain and calls a protected request() on the base Client. If you need a missing resource (e.g. Webhooks), subclass Netvlies\Basecamp\Client and add a webhooks() accessor returning your own resource class — cheaper than forking.
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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr