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 Xero Laravel Package

dcblogdev/laravel-xero

Laravel package for integrating the Xero accounting API into your app. Handles OAuth 2.0 authentication, token refresh, and provides a clean interface for invoices, contacts, payments, and other Xero endpoints, so you can sync accounting data without writing boilerplate API code.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. composer require dcblogdev/laravel-xero
  2. Publish the config and migration: php artisan vendor:publish --provider="Dcblogdev\Xero\XeroServiceProvider" (config lands at config/xero.php, migration creates the xero_tokens table). Run php artisan migrate.
  3. Add credentials to .env: XERO_CLIENT_ID, XERO_CLIENT_SECRET, XERO_REDIRECT_URI. The redirect URI must match the Xero app config byte-for-byte, including trailing slash.
  4. Wire up the two OAuth routes — one that calls Xero::connect() (redirects to Xero) and one that hits Xero::callback() (stores tokens). A simple Route::get('/xero/connect', ...) / Route::get('/xero/callback', ...) pair is enough to start.
  5. Look first at config/xero.php — that's where scopes, the redirect path, and the token model live. Then read the Xero facade class; every API call funnels through it.

First use case: connect a tenant, then Xero::get('Contacts') (or Xero::get('Organisation')) in a tinker session to prove the token round-trip works before writing any domain code.

Note for this release: accounting.transactions is no longer in the default scopes array. If your first use case involves invoices, credit notes, payments, bank transactions, or manual journals, add it back to config/xero.php before you run the OAuth flow — otherwise you'll get a valid token that can't touch the endpoints you care about.

Implementation Patterns

  • Facade-first, thin wrappers. Keep Xero::get/post/put/delete calls inside small repository or service classes (XeroInvoiceRepository) rather than scattering them across controllers. The facade methods return decoded arrays, so wrapping them in DTOs pays off quickly.
  • Token handling. Tokens are stored per-tenant in xero_tokens. Access tokens expire in ~30 minutes; the package refreshes on demand. Don't cache the access token yourself — cache the result of the call instead, and let the package own the refresh cycle.
  • Multi-tenant. After the callback, Xero::getTenants() gives you the connected organisations. Store the chosen tenantId on your User/Team model and call Xero::setTenantId($id) (or pass it per request) before each operation. If you only ever have one tenant, still read it from the token row rather than hardcoding.
  • Queued jobs. Wrap Xero writes in jobs so a 429 from the rate limiter retries instead of failing a user request. Use $backoff and $tries on the job; Xero's per-tenant limit is tight enough that a bulk sync will hit it.
  • Service layer + idempotency. For pushes (creating invoices, contacts), key off your own record ID and check Xero for an existing InvoiceNumber/ContactNumber before POSTing. Xero will happily create duplicates.
  • Webhooks. If you register Xero webhooks, verify the x-xero-signature HMAC against the raw request body before any middleware mutates it, and return 200 fast — enqueue the actual processing.
  • Scopes as a first-class concern. Keep your required scopes in your own config or a constant and merge them into config('xero.scopes') in a service provider. That way a package default change (like this release) can't silently break a feature.

Gotchas and Tips

  • v1.1.18 removed accounting.transactions from the default scopes. This is the headline change and it is effectively a breaking change for any app that relies on the default scope list. Symptoms are 401 Unauthorized / 403 Forbidden on invoice, payment, bank-transaction, credit-note, and manual-journal endpoints after a fresh connect, while contacts and settings keep working. Fix: add 'accounting.transactions' to scopes in config/xero.php and complete the OAuth flow again.
  • Existing tokens are not retroactively broken, but they are not fixed either. Scopes are baked into the token at consent time. Existing connections keep whatever scopes they were granted; a new scope requires the user to re-authorise. Don't assume a config edit alone fixes a live tenant.
  • Re-consent requires offline_access. Refresh tokens only come back if that scope is present — it's in the defaults, but if you prune the scopes array aggressively you'll silently lose refresh and get hourly reconnects.
  • 401s mid-flight. The refresh happens transparently, but if the refresh token itself is revoked (user removed the app in Xero), you get a hard failure. Catch it and route the user back through /xero/connect, don't just log and drop the job.
  • Rate limits. 60 calls/minute per tenant, 5,000/day. Bulk syncs need chunking plus usleep/RateLimiter::attempt — a naive foreach over 500 contacts will 429.
  • Redirect URI mismatch is the most common first-run failure, and Xero returns the error to your callback route, not to the Xero screen. Log the full query string on the callback.
  • state parameter is used for CSRF on the OAuth round-trip. If you're behind a load balancer with sticky-session issues, the state check can fail intermittently — verify session persistence before assuming it's a package bug.
  • Extension points: the token model and the config file are the two places worth subclassing/overriding. Swapping the token model lets you attach tokens to a Team instead of a global table, which is what most multi-tenant apps end up needing.
  • Pin the version. Given this release changed defaults without a major bump, add a caret constraint floor of ^1.1.18 (or lock it) so a future scope tweak doesn't ship itself into production overnight.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource