marshmallow/laravel-linear
marshmallow/laravel-linear is a Laravel integration for Linear's issue-tracking API, offering a configured GraphQL client, facade, and fluent helpers to query and manage issues, projects, teams, and workflows from your Laravel app.
composer require marshmallow-packages/laravel-linear — v1.4.1 is a patch release on the 1.4 line, so ^1.4 is a safe constraint.php artisan vendor:publish --tag=linear-config (check the README for the exact tag in your version)..env as LINEAR_API_KEY. Never commit it; the package reads it from config/linear.php.config/linear.php — endpoint, key, default team, timeout, any caching knobs.src/LinearServiceProvider.php — shows whether the client is a singleton, how it's bound, and what's publishable.issues(), teams(), comments(), etc.).README.md and tests/ — the tests are the most accurate documentation of return shapes.php artisan tinker — resolve the client from the container, list teams, then fetch one issue. Once that works, wire an "create an issue from a bug report" path, which is the most common integration.Inject, don't app() everywhere. Resolve the client through the container in constructors (or type-hint it in jobs/commands) so you can swap it in tests. If the package registers a facade, prefer the facade in controllers/commands and constructor injection in services.
Issue creation from user input. The classic pattern is a support form or Handler::report() hook that creates a Linear issue:
teamId, stateId, and labelIds from config or a cached lookup — never hard-code UUIDs in blade/controllers.Sync/backfill. For anything beyond fire-and-forget, keep a local projection table (linear_issues with id, identifier, state, updated_at) and poll with a filter on updatedAt greater than your last successful run. Store the high-water mark in cache/DB, not in memory. Use jobs with WithoutOverlapping so a slow run doesn't stack.
Webhooks. Point a Linear webhook at a route, verify the signature from the header using the signing secret in config, then dispatch a job. Persist the event id (or a hash of payload+timestamp) and short-circuit duplicates — Linear retries deliveries.
Nullable fields from v1.4.1 onward. message is now nullable (string|null). Treat this as the general rule for Linear payloads: many fields are optional depending on the resource type. Normalise at the boundary — map the raw response into your own DTO/array with explicit defaults ('message' => $raw['message'] ?? '') — so the rest of your app never has to branch on null.
Testing. Fake the HTTP layer against the GraphQL endpoint with Http::fake([...]) and return fixture payloads captured from real calls. Add an explicit fixture with "message": null so a regression in your null handling fails CI rather than production. Assert on the request body (query + variables) for write operations, since that's where mistakes hide.
Config over code. Keep team IDs, default state, label sets, and the project mapping in config/linear.php, keyed by environment or product area. It makes staging vs production and multi-team setups a config change instead of a deploy.
200 OK with an errors array and a null data. If you only check $response->successful() you will silently drop issues. Always inspect errors and throw your own exception.message is now null-able (v1.4.1). Audit anything that type-hints string $message, does strlen()/trim()/htmlspecialchars() on it, or writes it into a NOT NULL column. On PHP 8.1+ passing null to those functions throws a deprecation/TypeError depending on the call. Make the column nullable (or coalesce to ''), and check rendering paths ({{ $comment->message }} is fine, {!! nl2br($comment->message) !!} is not). This is the only functional change in this release; the webpack bump is a dev-dependency only and does not affect runtime.first: 250 queries with nested fragments burn quota fast. Prefer narrow selections, paginate with cursors, and add retry with backoff on the HTTP client for 429s — but don't retry 400/401, which are configuration problems, not transient ones.ENG-123) are different fields; use the UUID for API calls and the key for display and user-facing lookups.query, variables, and the response errors (redact the key) on failure. Without the variables in the log, Argument 'teamId' is invalid is very hard to trace.message before deploying — the type widened, and PHP's tolerance for that depends entirely on how strictly you type-hinted.How can I help you explore Laravel packages today?