kavist/rajaongkir
Laravel wrapper for the RajaOngkir API that simplifies shipping cost calculation and Indonesian province/city lookups, making it easy to integrate ongkir rates into e-commerce checkout flows with clean, ready-to-use methods.
RajaOngkir is an Indonesian shipping-cost API wrapper. Install with composer require kavist/rajaongkir. Laravel auto-discovery registers Kavist\RajaOngkir\Providers\RajaOngkirServiceProvider and the RajaOngkir facade.
composer require kavist/rajaongkir
php artisan vendor:publish --provider="Kavist\RajaOngkir\Providers\RajaOngkirServiceProvider"
Set RAJAONGKIR_API_KEY and your account type (starter, basic, pro) in .env, mapped through config/rajaongkir.php.
First use case — resolve a city, then price a shipment:
use Kavist\RajaOngkir\Facades\RajaOngkir;
$cities = RajaOngkir::kota()->dariProvinsi(12)->get(); // list cities in a province
$costs = RajaOngkir::ongkosKirim([
'origin' => 501, // city id
'destination' => 114,
'weight' => 1300, // grams, integer
'courier' => 'jne',
])->get();
Look first at:
config/rajaongkir.php — API key, account type, cache settings.Kavist\RajaOngkir\Facades\RajaOngkir — the fluent entry point (provinsi(), kota(), kecamatan(), ongkosKirim()).Kavist\RajaOngkir\HttpClients\BasicClient — the transport. As of v1.1.0 this no longer uses cURL; it uses file_get_contents() with a stream context. That is the single most important thing to understand about your runtime requirements.Kavist\RajaOngkir\HttpClients\ClientInterface — the swap point if you need a different transport.Keep the facade out of controllers. Wrap it in a ShippingCostService (or repository) that accepts a small request DTO and returns an array of {courier, service, description, cost, etd}. RajaOngkir's raw payload shape differs per endpoint (rajaongkir.results is an object for a single city lookup and an array for lists/costs), so normalise it once in your own layer rather than at every call site.
Persist location IDs, not names. Cache provinces/cities in your own table or a long-lived cache key, and store the RajaOngkir city ID on your addresses. City IDs are stable; names are not. This also removes 90% of your API calls.
Always cache cost lookups. Tariffs change on the order of weeks, not seconds. Enable the package's cache in config (or wrap your service in Cache::remember) keyed on origin|destination|weight|courier. Without this a checkout page will hammer the API and you will hit rate limits.
Bulk / async work goes on a queue. Multi-courier comparisons (JNE + TIKI + POS for one shipment) are three HTTP round-trips. Dispatch a job, cache the result, and render from cache. Never do a courier loop inside a synchronous web request if default_socket_timeout is 60s.
Swap the transport when you need control. Bind your own implementation of Kavist\RajaOngkir\HttpClients\ClientInterface in a service provider (a Guzzle-backed client is the usual choice). This is the supported escape hatch and it's more valuable after v1.1.0 than before, because the default client's behaviour is now tied to PHP INI settings rather than to ext-curl.
Test with a fake client, not Http::fake(). The package does not use Laravel's HTTP client, so Http::fake() will not intercept anything. Bind a stub ClientInterface returning fixture JSON in your test case's setUp().
Account type gates your endpoints. Sub-district (kecamatan) lookups and sub-district origin/destination types require a pro account. If you write code against sub-districts on a starter key you get a 200 response with an empty or error-shaped body, not an exception.
v1.1.0 requires allow_url_fopen=1. This is the release's main risk. BasicClient now calls file_get_contents('https://...'), so any environment with allow_url_fopen = Off — common on hardened shared hosting, and the default in some hardened PHP images — will throw file_get_contents(): https:// wrapper is disabled in the server configuration. Check with php -i | grep allow_url_fopen in every environment, including CI, before upgrading. If it's off, either enable it, or bind a Guzzle client.
You still need ext-openssl for the HTTPS wrapper. Losing the cURL dependency does not mean losing all extension requirements.
Error bodies may be silently lost. cURL returned the response body even on 4xx/5xx; file_get_contents() returns false and emits a warning unless the stream context sets ignore_errors. RajaOngkir reports validation errors (bad weight, wrong courier, invalid key) as a JSON rajaongkir.status payload — those diagnostics can now disappear. In production, wrap calls in try/catch, log error_get_last(), and, if you're debugging a mysterious empty result, temporarily verify with a raw curl call to the same URL to confirm the API is actually rejecting you.
Timeouts are now governed by INI, not by code. There is no per-request CURLOPT_TIMEOUT equivalent you can set from the package's public API — you inherit default_socket_timeout (60s by default). A slow RajaOngkir response can hold a PHP-FPM worker for a minute. Set a short timeout via your client binding if this matters.
Set a user_agent in php.ini. file_get_contents() sends no User-Agent unless user_agent is configured. Some proxies/WAFs in front of the API reject blank UAs. This is a classic "works locally, 403s in staging" cause.
Cache keys must be normalised. weight must be an integer in grams, courier lowercase. jne and JNE produce different cache entries (and possibly different behaviour upstream). Round odd weights to the courier's billing increment before keying.
Config caching matters for the key. With php artisan config:cache, a RAJAONGKIR_API_KEY change requires a re-cache. Never let the API key reach the browser — all calls server-side only.
Never trust an empty costs array as "no service". It can also mean an account-type mismatch, an invalid city ID, or a swallowed HTTP error. Distinguish these by inspecting rajaongkir.status.code in the payload — but note that after v1.1.0 you may need to log the raw response to see it at all.
Upgrade note for 1.0.x users: the public API (RajaOngkir::kota(), RajaOngkir::ongkosKirim(), etc.) is unchanged, so application code should not need edits. The breakage surface is environmental: allow_url_fopen, error-body visibility, and timeout behaviour. Treat v1.1.0 as a deployment-requirements change, not a code change.
How can I help you explore Laravel packages today?