yehia-tarek / laravel-erpnext
A Laravel package for interacting with ERPNext / Frappe REST API
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.5
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- mockery/mockery: ^1.5
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
README
A robust, fluent Laravel package for seamlessly interacting with the ERPNext / Frappe REST API. It provides an elegant interface for CRUD operations, a powerful query builder, typed document resources, file uploads, and multiple authentication strategies.
📋 Table of Contents
- Requirements
- Installation
- Configuration
- Authentication
- Quick Start
- Usage
- Advanced Features
- Error Handling
- Testing
- License
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.1 |
| Laravel | ^10.0 or ^11.0 |
| Guzzle | ^7.5 |
Installation
You can install the package via Composer:
composer require yehia-tarek/laravel-erpnext
Publish the configuration file to define your ERPNext connection settings:
php artisan vendor:publish --tag="erpnext-config"
Configuration
Add your ERPNext credentials to your application's .env file. The package supports multiple authentication methods, with Token Authentication being the recommended approach for API integrations.
ERPNEXT_BASE_URL=https://mycompany.erpnext.com # --- Token Auth (Recommended) --- ERPNEXT_AUTH_METHOD=token ERPNEXT_API_KEY=your_api_key ERPNEXT_API_SECRET=your_api_secret # --- Password Auth (Session-based) --- # ERPNEXT_AUTH_METHOD=password # ERPNEXT_USERNAME=admin # ERPNEXT_PASSWORD=secret # --- OAuth 2.0 --- # ERPNEXT_AUTH_METHOD=oauth # ERPNEXT_ACCESS_TOKEN=your_bearer_token # --- HTTP Options --- ERPNEXT_TIMEOUT=30 ERPNEXT_VERIFY_SSL=true
Generating API Keys in ERPNext
To use Token Authentication, generate API keys from your ERPNext instance:
- Go to User List → open the desired user.
- Click on the Settings tab.
- Expand the API Access section and click Generate Keys.
- Copy the API Secret immediately (it is only shown once).
- Note the API Key displayed in the same section.
Authentication
The package automatically handles authentication based on your .env configuration.
- Token: Sends
Authorization: token {api_key}:{api_secret}with every request. - Password: Performs a login request on the first API call and reuses the returned cookie session for subsequent requests.
- OAuth 2.0: Sends
Authorization: Bearer {access_token}with every request.
Quick Start
After configuring your .env file, you can verify the connection using the ERPNext facade:
use YehiaTarek\ERPNext\Facades\ERPNext; $user = ERPNext::getLoggedUser(); // Returns: "admin@example.com"
Usage
CRUD Operations
The package provides intuitive methods to interact with ERPNext documents.
Create a Document
$invoice = ERPNext::createDocument('Sales Invoice', [ 'customer' => 'ACME Corp', 'items' => [ ['item_code' => 'ITEM-001', 'qty' => 2, 'rate' => 150], ], ]); echo $invoice['name']; // e.g., "SINV-00001"
Read a Document
$invoice = ERPNext::getDocument('Sales Invoice', 'SINV-00001'); echo $invoice['grand_total']; // Expand link fields to fetch full related documents instead of just their names $expandedInvoice = ERPNext::getDocument('Sales Invoice', 'SINV-00001', expandLinks: true); echo $expandedInvoice['customer']['customer_name']; // Returns the full Customer document
Update a Document
ERPNext::updateDocument('Sales Invoice', 'SINV-00001', [ 'status' => 'Paid', ]);
Delete a Document
ERPNext::deleteDocument('Sales Invoice', 'SINV-00001'); // Returns true on success
Fluent Query Builder
Retrieve multiple documents using a fluent, Eloquent-like query builder that wraps the GET /api/resource/:doctype endpoint.
use YehiaTarek\ERPNext\Facades\ERPNext; $invoices = ERPNext::query('Sales Invoice') ->fields(['name', 'customer', 'grand_total', 'status']) ->filter('status', '=', 'Paid') ->filter('grand_total', '>', 5000) ->orderBy('grand_total', 'desc') ->limit(25) ->get(); // Returns an array of arrays
Available Builder Methods
| Method | Description |
|---|---|
fields(array) |
Specify which fields to fetch. |
filter(field, op, value) |
Add an AND filter condition. |
filters(array) |
Add multiple AND filters at once. |
orFilter(field, op, value) |
Add an OR filter condition. |
expand(array) |
Expand specific link fields inline. |
orderBy(field, direction) |
Sort results (asc or desc). |
limit(int) |
Limit the number of records returned. |
offset(int) |
Skip a specific number of records. |
paginate(page, perPage) |
Utilize page-based pagination. |
asList() |
Return results as a List[List] instead of List[dict]. |
debug() |
Include executed SQL in the response. |
get() |
Execute the query and return an array. |
first() |
Execute and return the first item or null. |
count() |
Return the total count of matching documents. |
Supported Filtering Operators
->filter('status', '=', 'Paid') ->filter('amount', '>', 1000) ->filter('amount', '>=', 500) ->filter('name', 'like', 'SINV-%') ->filter('status', 'in', ['Paid', 'Unpaid']) ->filter('status', 'not in', ['Cancelled']) ->filter('note', '!=', null)
Remote Method Calls
Call any whitelisted Python method on your ERPNext instance.
// GET method (read-only) $user = ERPNext::callGet('frappe.auth.get_logged_user'); // POST method (mutates data) ERPNext::callPost('frappe.client.submit', [ 'doc' => ['doctype' => 'Sales Invoice', 'name' => 'SINV-00001'], ]); // Generic call specifying the HTTP verb explicitly ERPNext::call('erpnext.accounts.doctype.payment_entry.payment_entry.get_outstanding_reference_documents', [ 'args' => ['party_type' => 'Customer', 'party' => 'CUST-001'], ], 'POST');
File Uploads
Upload files directly from your local filesystem or via raw content.
Upload from Local Path
$file = ERPNext::uploadFile( filePath: storage_path('app/invoice.pdf'), doctype: 'Sales Invoice', docname: 'SINV-00001', fieldname: 'attachment', isPrivate: true, ); echo $file['file_url'];
Upload from Raw Content
$file = ERPNext::uploadFileContent( content: $pdfContent, filename: 'report.pdf', doctype: 'Sales Invoice', docname: 'SINV-00001', );
Advanced Features
Typed Document Resources
For a more structured, object-oriented approach, you can define typed resources that extend the base Document class. This provides an Eloquent-like experience for specific DocTypes.
namespace App\ERPNext; use YehiaTarek\ERPNext\Resources\Document; class SalesInvoice extends Document { protected static string $doctype = 'Sales Invoice'; }
Usage Example:
use App\ERPNext\SalesInvoice; // Query $paid = SalesInvoice::query() ->fields(['name', 'customer', 'grand_total']) ->filter('status', '=', 'Paid') ->orderBy('modified', 'desc') ->get(); // Find $invoice = SalesInvoice::find('SINV-00001'); echo $invoice->grand_total; // Find or fail (throws DocumentNotFoundException) $invoice = SalesInvoice::findOrFail('SINV-00001'); // Create $invoice = SalesInvoice::create([ 'customer' => 'ACME Corp', 'items' => [...], ]); // Update (partial) $invoice->update(['status' => 'Paid']); // Save (create or update based on whether 'name' is present) $invoice = new SalesInvoice(['customer' => 'ACME Corp', ...]); $invoice->save(); // Delete & Refresh $invoice->delete(); $invoice->refresh();
Multiple Connections
If you need to interact with multiple ERPNext instances (e.g., staging and production), define them in config/erpnext.php:
return [ 'base_url' => env('ERPNEXT_BASE_URL'), 'auth' => [...], 'connections' => [ 'staging' => [ 'base_url' => env('ERPNEXT_STAGING_URL'), 'auth' => [ 'method' => 'token', 'api_key' => env('ERPNEXT_STAGING_API_KEY'), 'api_secret' => env('ERPNEXT_STAGING_API_SECRET'), ], ], ], ];
You can easily switch connections using the connection() method:
$stagingInvoice = ERPNext::connection('staging')->getDocument('Sales Invoice', 'SINV-00001');
Error Handling
The package throws specific exceptions for different API error scenarios, allowing you to handle them gracefully.
| Exception | Trigger |
|---|---|
AuthenticationException |
401/403 responses or missing credentials. |
DocumentNotFoundException |
404 response from ERPNext. |
ValidationException |
ERPNext ValidationError (422). |
ERPNextException |
Fallback for all other API errors. |
Example:
use YehiaTarek\ERPNext\Exceptions\DocumentNotFoundException; use YehiaTarek\ERPNext\Exceptions\ValidationException; use YehiaTarek\ERPNext\Exceptions\ERPNextException; try { $doc = ERPNext::getDocument('Sales Invoice', 'SINV-GHOST'); } catch (DocumentNotFoundException $e) { // Handle 404 } catch (ValidationException $e) { $context = $e->getContext(); // Raw response body as array } catch (ERPNextException $e) { // Handle generic API errors }
Testing
Run the package tests using Composer:
composer test
Mocking in Your Application Tests
You can easily mock the ERPNext facade in your application's test suite:
use YehiaTarek\ERPNext\Facades\ERPNext; ERPNext::shouldReceive('getDocument') ->with('Sales Invoice', 'SINV-00001', false) ->andReturn(['name' => 'SINV-00001', 'status' => 'Paid']);
License
The MIT License (MIT). Please see LICENSE for more information.