A modern PHP client for XIVAPI v2 — the Final Fantasy XIV game data API.
- Introduction
- Requirements
- Installation
- Quick Start
- Global Configuration
- API Reference
- Query Builders
- Response Classes
- Error Handling
- Debugging
- Enums Reference
- Examples
- License
XIVAPI provides access to Final Fantasy XIV game data including items, actions, quests, and more. This library offers a clean, fluent PHP interface for the XIVAPI v2 endpoints.
Features:
- Full coverage of all XIVAPI v2 endpoints
- Fluent query builders for fields and search queries
- PSR-18 HTTP client compatibility (use any HTTP client you prefer)
- Strong typing with PHP 8.3 enums
- Global configuration for language, version, and schema
For the official API documentation, visit v2.xivapi.com/docs.
- PHP 8.3 or higher
- A PSR-18 HTTP client (e.g., Guzzle, Symfony HttpClient)
- A PSR-17 HTTP factory (usually bundled with the HTTP client)
Install via Composer:
composer require izaghar/xivapi-phpYou also need a PSR-18 compatible HTTP client. Here are some options:
With Guzzle:
composer require guzzlehttp/guzzleWith Symfony HttpClient:
composer require symfony/http-client nyholm/psr7use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use XivApi\XivApi;
use XivApi\Enums\Language;
// Create the API client
$api = new XivApi(
new Client(),
new HttpFactory()
);
// Fetch an item
$item = $api->sheet('Item')->row(4)->get();
echo $item->fields['Name']; // "Wind Shard"
// Search for items
$results = $api->search()
->query('Name~"Potion"')
->sheets(['Item'])
->get();
foreach ($results->results as $result) {
echo $result->fields['Name'] . "\n";
}Configure default settings that apply to all requests:
use XivApi\Enums\Language;
$api = (new XivApi($http, $factory))
->language(Language::German) // Default language for all requests
->gameVersion('7.0') // Lock to a specific game version
->schema('exdschema@latest') // Use a specific schema
->localizations(Language::English, Language::Japanese); // For field expansionAll settings are optional and can be overridden per-request.
| Method | Description |
|---|---|
language(Language $lang) |
Set the default language for text fields |
gameVersion(string $version) |
Lock requests to a specific game version |
schema(string $schema) |
Use a specific schema for field definitions |
localizations(Language ...$langs) |
Languages used when expanding localized fields |
List all available game versions.
$response = $api->version()->get();
foreach ($response->versions as $version) {
echo $version->key . ': ' . implode(', ', $version->names) . "\n";
}
// Output: "2139246928a48a9a: 7.01, latest"Endpoint: GET /version
List all available sheets (Excel files containing game data).
$response = $api->sheetIndex()->list();
foreach ($response->sheets as $sheetName) {
echo $sheetName . "\n";
}
// Output: "Action", "Item", "Quest", ...Endpoint: GET /sheet
Available Methods:
| Method | Description |
|---|---|
version(string $v) |
Use a specific game version |
list() |
Execute and return SheetListResponse |
getUrl() |
Get the request URL (for debugging) |
Query multiple rows from a sheet.
// Basic query
$items = $api->sheet('Item')
->fields('Name,Icon,Description')
->language(Language::English)
->limit(50)
->get();
foreach ($items->rows as $row) {
echo $row->rowId . ': ' . $row->fields['Name'] . "\n";
}Endpoint: GET /sheet/{sheet}
Available Methods:
| Method | Description |
|---|---|
fields(string|Field|array $fields) |
Select specific fields |
transient(string|Field|array $fields) |
Select fields from transient sheet |
language(Language $lang) |
Set the language |
schema(string $schema) |
Use a specific schema |
version(string $v) |
Use a specific game version |
limit(int $n) |
Limit number of rows returned |
after(string|int $rowId) |
Pagination: fetch rows after this ID |
rows(array $ids) |
Fetch specific row IDs |
get() |
Execute and return SheetResponse |
getUrl() |
Get the request URL (for debugging) |
// First page
$page1 = $api->sheet('Item')->limit(100)->get();
// Next page (using the last row ID)
$lastRowId = end($page1->rows)->rowId;
$page2 = $api->sheet('Item')->limit(100)->after($lastRowId)->get();$items = $api->sheet('Item')
->rows([1, 2, 3, 4, 5])
->get();Fetch a single row by ID.
$item = $api->sheet('Item')->row(4)->get();
echo $item->rowId; // 4
echo $item->fields['Name']; // "Wind Shard"
echo $item->version; // "2139246928a48a9a"
echo $item->schema; // "exdschema@..."Endpoint: GET /sheet/{sheet}/{row}
Available Methods:
| Method | Description |
|---|---|
fields(string|Field|array $fields) |
Select specific fields |
transient(string|Field|array $fields) |
Select fields from transient sheet |
language(Language $lang) |
Set the language |
schema(string $schema) |
Use a specific schema |
version(string $v) |
Use a specific game version |
get() |
Execute and return RowResponse |
getUrl() |
Get the request URL (for debugging) |
Some sheets have subrows (e.g., 262144:0, 262144:1). Access them with a string:
$row = $api->sheet('GilShopItem')->row('262144:1')->get();
echo $row->rowId; // 262144
echo $row->subrowId; // 1Search across one or more sheets using a query string.
$results = $api->search()
->query('Name~"Potion" +Level>=1')
->sheets(['Item'])
->fields('Name,Icon,LevelItem')
->limit(20)
->get();
foreach ($results->results as $result) {
echo sprintf(
"[%s] %s #%d: %s (score: %.2f)\n",
$result->sheet,
$result->rowId,
$result->fields['Name'],
$result->score
);
}Endpoint: GET /search
Available Methods:
| Method | Description |
|---|---|
query(string|SearchQuery $q) |
The search query |
sheets(array $sheets) |
Sheets to search in |
fields(string|Field|array $fields) |
Select specific fields |
transient(string|Field|array $fields) |
Select fields from transient sheet |
language(Language $lang) |
Set the language |
schema(string $schema) |
Use a specific schema |
version(string $v) |
Use a specific game version |
limit(int $n) |
Limit number of results |
cursor(string $cursor) |
Continue from a previous search |
get() |
Execute and return SearchResponse |
getUrl() |
Get the request URL (for debugging) |
// Partial string match (contains)
'Name~"Potion"'
// Exact equality
'LevelItem=50'
// Numeric comparisons
'LevelItem>=50'
'LevelItem<10'
// Required clause (must match)
'+Name~"Potion"'
// Excluded clause (must not match)
'-Name~"Hi-Potion"'
// Nested fields
'ItemUICategory.Name="Medicine"'
// Language-specific
'Name@ja~"ポーション"'
// Array elements
'BaseParam[].Name="Strength"'
// Grouping
'+(Name~"Potion" Name~"Ether") +LevelItem>=1'$page1 = $api->search()
->query('Name~"Potion"')
->sheets(['Item'])
->limit(20)
->get();
if ($page1->hasMore()) {
$page2 = $api->search()
->cursor($page1->next)
->get();
}Fetch game assets (icons, textures) in various formats.
// Get PNG image data
$imageData = $api->asset('ui/icon/051000/051474_hr1.tex')->get();
file_put_contents('icon.png', $imageData);
// Different format
use XivApi\Enums\AssetFormat;
$jpg = $api->asset('ui/loadingimage/title/ffxiv_logo_jpen.tex', AssetFormat::Jpg)->get();Endpoint: GET /asset
Available Methods:
| Method | Description |
|---|---|
version(string $v) |
Use a specific game version |
get() |
Execute and return binary image data |
fetch() |
Execute and return full PSR-7 Response |
getUrl() |
Get the request URL (for debugging) |
$response = $api->asset('ui/icon/051000/051474_hr1.tex')->fetch();
$etag = $response->getHeaderLine('ETag');
$contentType = $response->getHeaderLine('Content-Type');
$body = $response->getBody()->getContents();Fetch composed map images.
// Get a map image (JPEG)
$mapData = $api->map('s1d1', '00')->get();
file_put_contents('map.jpg', $mapData);Endpoint: GET /asset/map/{territory}/{index}
Parameters:
territory— Territory ID (e.g.,s1d1,r1f1)index— Map index, zero-padded (e.g.,00,01)
Available Methods:
| Method | Description |
|---|---|
version(string $v) |
Use a specific game version |
get() |
Execute and return binary image data |
fetch() |
Execute and return full PSR-7 Response |
getUrl() |
Get the request URL (for debugging) |
For complex field selections, use the Field class:
use XivApi\Query\Field;
use XivApi\Enums\Language;
use XivApi\Enums\Transform;
$api->sheet('Item')
->fields([
Field::make('Name'),
Field::make('Description')->as(Transform::Html),
Field::make('ItemUICategory.Name'),
])
->get();| Method | Description | Example Output |
|---|---|---|
make(string $name) |
Create a field | Name |
lang(Language $l) |
Set explicit language | Name@lang(de) |
as(Transform $t) |
Apply transformation | Description@as(html) |
localized(Language ...$l) |
Expand to multiple languages | Name,Name@lang(de),Name@lang(fr) |
Get a field in multiple languages at once:
// Explicit languages
Field::make('Name')->localized(Language::German, Language::French)
// Builds: Name,Name@lang(de),Name@lang(fr)
// Use globally configured languages
$api = (new XivApi($http, $factory))
->localizations(Language::German, Language::French);
$api->sheet('Item')
->fields([
Field::make('Name')->localized(), // Uses global languages
Field::make('Icon'), // Not localized
])
->get();// HTML formatting for strings with markup
Field::make('Description')->as(Transform::Html)
// Raw value (skip relation resolution)
Field::make('ItemUICategory')->as(Transform::Raw)Use dot-notation for nested fields:
Field::make('ItemUICategory.Name')
Field::make('ItemUICategory.Name')->lang(Language::Japanese)Build search queries programmatically instead of writing raw query strings:
use XivApi\Query\SearchQuery;
use XivApi\Enums\Language;
// Simple conditions
$query = SearchQuery::where('LevelItem', 50);
$query = SearchQuery::where('Name')->contains('Potion');
// Shortcuts
$query = SearchQuery::where('Name', 'Potion') // equals
->where('Level', '>=', 90); // greaterOrEqual
// Chained conditions
$query = SearchQuery::where('Name')->contains('Potion')
->where('LevelItem')->greaterOrEqual(10)
->whereNot('Name')->contains('Hi-');
// Pass to search
$api->search()
->query($query)
->sheets(['Item'])
->get();Note: You can also use
SearchQuery::make()to create an empty instance first.
| Method | Prefix | Description |
|---|---|---|
where($field) |
+ |
Must match |
whereNot($field) |
- |
Must not match |
orWhere($field) |
(none) | OR / optional |
->where('Field', $value) // equals
->where('Field', '=', $value) // equals
->where('Field', '~', $value) // contains
->where('Field', '>', $value) // greaterThan
->where('Field', '<', $value) // lessThan
->where('Field', '>=', $value) // greaterOrEqual
->where('Field', '<=', $value) // lessOrEqual| Method | Query Syntax |
|---|---|
equals($value) |
Field=value |
contains(string $value) |
Field~"value" |
greaterThan($value) |
Field>value |
lessThan($value) |
Field<value |
greaterOrEqual($value) |
Field>=value |
lessOrEqual($value) |
Field<=value |
// AND group (all conditions must match)
$query = SearchQuery::where('LevelItem')->greaterOrEqual(1)
->whereGroup(fn($g) => $g
->where('Name')->contains('Potion')
->where('Name')->contains('Ether')
);
// Builds: +LevelItem>=1 +(+Name~"Potion" +Name~"Ether")
// OR group (at least one condition must match)
$query = SearchQuery::where('LevelItem')->greaterOrEqual(1)
->whereGroup(fn($g) => $g
->orWhere('Name')->contains('Potion')
->orWhere('Name')->contains('Ether')
);
// Builds: +LevelItem>=1 +(Name~"Potion" Name~"Ether")
// Must not group
->whereNotGroup(fn($g) => ...) // -(...)
// OR group
->orWhereGroup(fn($g) => ...) // (...)// Search in array elements
$query = SearchQuery::whereHas('BaseParam', fn($q) => $q
->where('Name')->equals('Strength')
);
// Builds: +BaseParam[].Name="Strength"
// Variants
->whereHas($array, $callback) // +Array[].Field=value
->whereHasNot($array, $callback) // -Array[].Field=value
->orWhereHas($array, $callback) // Array[].Field=value$query = SearchQuery::where('Name')->localizedTo(Language::Japanese)->contains('ポーション');
// Builds: +Name@ja~"ポーション"$query = SearchQuery::where('IsFlying', true)
->where('ExtraSeats', '>', 0)
->orWhere('Name')->localizedTo(Language::Japanese)->contains('ドラゴン')
->whereGroup(fn($q) => $q
->orWhere('Level', 80)
->orWhere('Level', 90)
);
// Builds: +IsFlying=true +ExtraSeats>0 Name@ja~"ドラゴン" +(Level=80 Level=90)| Class | Returned By | Key Properties |
|---|---|---|
VersionsResponse |
version()->get() |
versions[] |
Version |
— | key, names[] |
SheetListResponse |
sheetIndex()->list() |
sheets[] |
SheetResponse |
sheet()->get() |
rows[], version, schema |
RowResponse |
sheet()->row()->get() |
rowId, subrowId, fields, transient, version, schema |
Row |
— | rowId, subrowId, fields, transient |
SearchResponse |
search()->get() |
results[], next, version, schema |
SearchResult |
— | sheet, rowId, subrowId, score, fields, transient |
$response = $api->search()->query('...')->sheets(['Item'])->get();
if ($response->hasMore()) {
// Use $response->next as cursor for next page
}All API errors throw XivApiException:
use XivApi\Exception\XivApiException;
try {
$api->sheet('InvalidSheet')->get();
} catch (XivApiException $e) {
echo $e->getMessage(); // Error message from API
echo $e->statusCode; // HTTP status code (e.g., 404)
}Every client has a getUrl() method to inspect the request URL:
$url = $api->sheet('Item')
->fields('Name,Icon')
->language(Language::German)
->limit(10)
->getUrl();
echo $url;
// https://v2.xivapi.com/api/sheet/Item?fields=Name,Icon&language=de&limit=10use XivApi\Enums\Language;
Language::Japanese // ja
Language::English // en
Language::German // de
Language::French // fruse XivApi\Enums\Transform;
Transform::Raw // Skip relation resolution
Transform::Html // Format string as HTMLuse XivApi\Enums\AssetFormat;
AssetFormat::Png // PNG image (default)
AssetFormat::Jpg // JPEG image
AssetFormat::Webp // WebP imageSee the examples/Readme.md for runnable code samples:
- Versions — List game versions
- Sheets — Query sheet data with pagination
- Search — Various search patterns
- Assets — Download icons and maps
MIT License. See LICENSE for details.