Skip to content

Commit 1b94e4b

Browse files
authored
Merge pull request #16057 from leandrocfe/table-external-api-example
Docs V4 - Table - external api examples
2 parents 4743509 + 16cbd5c commit 1b94e4b

File tree

1 file changed

+361
-0
lines changed

1 file changed

+361
-0
lines changed

Diff for: packages/tables/docs/09-static-data.md

+361
Original file line numberDiff line numberDiff line change
@@ -417,3 +417,364 @@ public function table(Table $table): Table
417417
]);
418418
}
419419
```
420+
421+
## Using an external API as a table data source
422+
423+
[Filament's table builder](overview/#introduction) allows you to populate tables with data fetched from any external source—not just [Eloquent models](https://laravel.com/docs/eloquent). This is particularly useful when you want to display data from a REST API or a third-party service.
424+
425+
### Fetching data from an external API
426+
427+
The example below demonstrates how to consume data from [DummyJSON](https://dummyjson.com), a free fake REST API for placeholder JSON, and display it in a [Filament table](overview/#introduction):
428+
429+
```php
430+
use Filament\Tables\Columns\IconColumn;
431+
use Filament\Tables\Columns\TextColumn;
432+
use Filament\Tables\Table;
433+
use Illuminate\Support\Facades\Http;
434+
435+
public function table(Table $table): Table
436+
{
437+
return $table
438+
->records(fn (): array => Http::baseUrl('https://dummyjson.com')
439+
->get('products')
440+
->collect()
441+
->get('products', [])
442+
)
443+
->columns([
444+
TextColumn::make('title'),
445+
TextColumn::make('category'),
446+
TextColumn::make('price')
447+
->money(),
448+
]);
449+
}
450+
```
451+
452+
`get('products')` makes a `GET` request to [`https://dummyjson.com/products`](https://dummyjson.com/products). The `collect()` method converts the JSON response into a [Laravel collection](https://laravel.com/docs/collections#main-content). Finally, `get('products', [])` retrieves the array of products from the response. If the key is missing, it safely returns an empty array.
453+
454+
<Aside variant="warning">
455+
This is a basic example for demonstration purposes only. It's the developer's responsibility to implement proper authentication, authorization, validation, error handling, rate limiting, and other best practices when working with APIs.
456+
</Aside>
457+
458+
<Aside variant="info">
459+
DummyJSON returns 30 items by default. You can use the [limit and skip](#external-api-pagination) query parameters to paginate through all items or use [`limit=0`](https://dummyjson.com/docs/products#products-limit_skip) to get all items.
460+
</Aside>
461+
462+
#### Setting the state of a column using API data
463+
464+
[Columns](#columns) map to the array keys returned by the `records()` function.
465+
466+
When working with the current record inside a column function, set the `$record` type to `array` instead of `Model`. For example, to define a column using the [`state()`](columns/overview#setting-the-state-of-a-column) function, you could do the following:
467+
468+
```php
469+
use Filament\Tables\Columns\TextColumn;
470+
use Illuminate\Support\Str;
471+
472+
TextColumn::make('category_brand')
473+
->label('Category - Brand')
474+
->state(function (array $record): string {
475+
$category = Str::headline($record['category']);
476+
$brand = Str::title($record['brand'] ?? 'Unknown');
477+
478+
return "{$category} - {$brand}";
479+
})
480+
```
481+
482+
<Aside variant="tip">
483+
You can use the [`formatStateUsing()`](columns/text#formatting) method to format the state of a text column without changing the state itself.
484+
</Aside>
485+
486+
### External API sorting
487+
488+
You can enable [sorting](columns#sorting) in [columns](columns) even when using an external API as the data source. The example below demonstrates how to pass sorting parameters (`sort_column` and `sort_direction`) to the [DummyJSON](https://dummyjson.com/docs/products#products-sort) API and how they are handled by the API.
489+
490+
```php
491+
use Filament\Tables\Columns\TextColumn;
492+
use Filament\Tables\Table;
493+
use Illuminate\Support\Facades\Http;
494+
495+
public function table(Table $table): Table
496+
{
497+
return $table
498+
->records(function (?string $sortColumn, ?string $sortDirection): array {
499+
$response = Http::baseUrl('https://dummyjson.com/')
500+
->get('products', [
501+
'sortBy' => $sortColumn,
502+
'order' => $sortDirection,
503+
]);
504+
505+
return $response
506+
->collect()
507+
->get('products', []);
508+
})
509+
->columns([
510+
TextColumn::make('title'),
511+
TextColumn::make('category')
512+
->sortable(),
513+
TextColumn::make('price')
514+
->money(),
515+
]);
516+
}
517+
```
518+
`get('products')` makes a `GET` request to [`https://dummyjson.com/products`](https://dummyjson.com/products). The request includes two parameters: `sortBy`, which specifies the column to sort by (e.g., category), and `order`, which specifies the direction of the sort (e.g., asc or desc). The `collect()` method converts the JSON response into a [Laravel collection](https://laravel.com/docs/collections#main-content). Finally, `get('products', [])` retrieves the array of products from the response. If the key is missing, it safely returns an empty array.
519+
520+
<Aside variant="warning">
521+
This is a basic example for demonstration purposes only. It's the developer's responsibility to implement proper authentication, authorization, validation, error handling, rate limiting, and other best practices when working with APIs.
522+
</Aside>
523+
524+
<Aside variant="info">
525+
DummyJSON returns 30 items by default. You can use the [limit and skip](#external-api-pagination) query parameters to paginate through all items or use [`limit=0`](https://dummyjson.com/docs/products#products-limit_skip) to get all items.
526+
</Aside>
527+
528+
### External API searching
529+
530+
You can enable [searching](columns#searching) in [columns](columns) even when using an external API as the data source. The example below demonstrates how to pass the `search` parameter to the [DummyJSON](https://dummyjson.com/docs/products#products-search) API and how it is handled by the API.
531+
532+
```php
533+
use Filament\Tables\Columns\TextColumn;
534+
use Filament\Tables\Table;
535+
use Illuminate\Support\Facades\Http;
536+
537+
public function table(Table $table): Table
538+
{
539+
return $table
540+
->records(function (?string $search): array {
541+
$response = Http::baseUrl('https://dummyjson.com/')
542+
->get('products/search', [
543+
'q' => $search,
544+
]);
545+
546+
return $response
547+
->collect()
548+
->get('products', []);
549+
})
550+
->columns([
551+
TextColumn::make('title'),
552+
TextColumn::make('category'),
553+
TextColumn::make('price')
554+
->money(),
555+
])
556+
->searchable();
557+
}
558+
```
559+
560+
`get('products/search')` makes a `GET` request to [`https://dummyjson.com/products/search`](https://dummyjson.com/products/search). The request includes the `q` parameter, which is used to filter the results based on the `search` query. The `collect()` method converts the JSON response into a [Laravel collection](https://laravel.com/docs/collections#main-content). Finally, `get('products', [])` retrieves the array of products from the response. If the key is missing, it safely returns an empty array.
561+
562+
<Aside variant="warning">
563+
This is a basic example for demonstration purposes only. It's the developer's responsibility to implement proper authentication, authorization, validation, error handling, rate limiting, and other best practices when working with APIs.
564+
</Aside>
565+
566+
<Aside variant="info">
567+
DummyJSON returns 30 items by default. You can use the [limit and skip](#external-api-pagination) query parameters to paginate through all items or use [`limit=0`](https://dummyjson.com/docs/products#products-limit_skip) to get all items.
568+
</Aside>
569+
570+
### External API filtering
571+
572+
You can enable [filtering](filters) in your table even when using an external API as the data source. The example below demonstrates how to pass the `filter` parameter to the [DummyJSON](https://dummyjson.com/docs/products#products-search) API and how it is handled by the API.
573+
574+
```php
575+
use Filament\Tables\Columns\TextColumn;
576+
use Filament\Tables\Filters\SelectFilter;
577+
use Filament\Tables\Table;
578+
use Illuminate\Support\Collection;
579+
use Illuminate\Support\Facades\Http;
580+
581+
public function table(Table $table): Table
582+
{
583+
return $table
584+
->records(function (array $filters): array {
585+
$category = $filters['category']['value'] ?? null;
586+
587+
$endpoint = filled($category)
588+
? "products/category/{$category}"
589+
: 'products';
590+
591+
$response = Http::baseUrl('https://dummyjson.com/')
592+
->get($endpoint);
593+
594+
return $response
595+
->collect()
596+
->get('products', []);
597+
})
598+
->columns([
599+
TextColumn::make('title'),
600+
TextColumn::make('category'),
601+
TextColumn::make('price')
602+
->money(),
603+
])
604+
->filters([
605+
SelectFilter::make('category')
606+
->label('Category')
607+
->options(fn (): Collection => Http::baseUrl('https://dummyjson.com/')
608+
->get('products/categories')
609+
->collect()
610+
->pluck('name', 'slug')
611+
),
612+
]);
613+
}
614+
```
615+
616+
If a category filter is selected, the request is made to `/products/category/{category}`; otherwise, it defaults to `/products`. The `get()` method sends a `GET` request to the appropriate endpoint. The `collect()` method converts the JSON response into a [Laravel collection](https://laravel.com/docs/collections#main-content). Finally, `get('products', [])` retrieves the array of products from the response. If the key is missing, it safely returns an empty array.
617+
618+
<Aside variant="warning">
619+
This is a basic example for demonstration purposes only. It's the developer's responsibility to implement proper authentication, authorization, validation, error handling, rate limiting, and other best practices when working with APIs.
620+
</Aside>
621+
622+
<Aside variant="info">
623+
DummyJSON returns 30 items by default. You can use the [limit and skip](#external-api-pagination) query parameters to paginate through all items or use [`limit=0`](https://dummyjson.com/docs/products#products-limit_skip) to get all items.
624+
</Aside>
625+
626+
### External API pagination
627+
628+
You can enable [pagination](overview#pagination) when using an external API as the table data source. Filament will pass the current page and the number of records per page to your `records()` function. The example below demonstrates how to construct a `LengthAwarePaginator` manually and fetch paginated data from the [DummyJSON](https://dummyjson.com/docs/products#products-limit_skip) API, which uses `limit` and `skip` parameters for pagination:
629+
630+
```php
631+
public function table(Table $table): Table
632+
{
633+
return $table
634+
->records(function (int $page, int $recordsPerPage): LengthAwarePaginator {
635+
$skip = ($page - 1) * $recordsPerPage;
636+
637+
$response = Http::baseUrl('https://dummyjson.com')
638+
->get('products', [
639+
'limit' => $recordsPerPage,
640+
'skip' => $skip,
641+
])
642+
->collect();
643+
644+
return new LengthAwarePaginator(
645+
items: $response['products'],
646+
total: $response['total'],
647+
perPage: $recordsPerPage,
648+
currentPage: $page
649+
);
650+
})
651+
->columns([
652+
TextColumn::make('title'),
653+
TextColumn::make('category'),
654+
TextColumn::make('price')
655+
->money(),
656+
]);
657+
}
658+
```
659+
660+
`$page` and `$recordsPerPage` are automatically injected by Filament based on the current pagination state.
661+
The calculated `skip` value tells the API how many records to skip before returning results for the current page.
662+
The response contains `products` (the paginated items) and `total` (the total number of available items).
663+
These values are passed to a `LengthAwarePaginator`, which Filament uses to render pagination controls correctly.
664+
665+
<Aside variant="warning">
666+
This is a basic example for demonstration purposes only. It's the developer's responsibility to implement proper authentication, authorization, validation, error handling, rate limiting, and other best practices when working with APIs.
667+
</Aside>
668+
669+
### External API full example
670+
671+
This example demonstrates how to combine [sorting](#external-api-sorting), [search](#external-api-searching), [category filtering](#external-api-filtering), and [pagination](#external-api-pagination) when using an external API as the data source. The API used here is [DummyJSON](https://dummyjson.com), which supports these features individually but **does not allow combining all of them in a single request**. This is because each feature uses a different endpoint:
672+
673+
- [Search](#external-api-searching) is performed through the `/products/search` endpoint using the `q` parameter.
674+
- [Category filtering](#external-api-filtering) uses the `/products/category/{category}` endpoint.
675+
- [Sorting](#external-api-sorting) is handled by sending `sortBy` and `order` parameters to the `/products` endpoint.
676+
677+
The only feature that can be combined with each of the above is [pagination](#external-api-pagination), since the `limit` and `skip` parameters are supported across all three endpoints.
678+
679+
```php
680+
use Filament\Tables\Columns\ImageColumn;
681+
use Filament\Tables\Columns\TextColumn;
682+
use Filament\Tables\Filters\SelectFilter;
683+
use Filament\Tables\Table;
684+
use Illuminate\Pagination\LengthAwarePaginator;
685+
use Illuminate\Support\Collection;
686+
use Illuminate\Support\Facades\Http;
687+
use Illuminate\Support\Str;
688+
689+
public function table(Table $table): Table
690+
{
691+
$baseUrl = 'https://dummyjson.com/';
692+
693+
return $table
694+
->records(function (
695+
?string $sortColumn,
696+
?string $sortDirection,
697+
?string $search,
698+
array $filters,
699+
int $page,
700+
int $recordsPerPage
701+
) use ($baseUrl): LengthAwarePaginator {
702+
// Get the selected category from filters (if any)
703+
$category = $filters['category']['value'] ?? null;
704+
705+
// Choose endpoint depending on search or filter
706+
$endpoint = match (true) {
707+
filled($search) => 'products/search',
708+
filled($category) => "products/category/{$category}",
709+
default => 'products',
710+
};
711+
712+
// Determine skip offset
713+
$skip = ($page - 1) * $recordsPerPage;
714+
715+
// Base query parameters for all requests
716+
$params = [
717+
'limit' => $recordsPerPage,
718+
'skip' => $skip,
719+
'select' => 'id,title,brand,category,thumbnail,price,sku,stock',
720+
];
721+
722+
// Add search query if applicable
723+
if (filled($search)) {
724+
$params['q'] = $search;
725+
}
726+
727+
// Add sorting parameters
728+
if ($endpoint === 'products' && $sortColumn) {
729+
$params['sortBy'] = $sortColumn;
730+
$params['order'] = $sortDirection ?? 'asc';
731+
}
732+
733+
$response = Http::baseUrl($baseUrl)
734+
->get($endpoint, $params)
735+
->collect();
736+
737+
return new LengthAwarePaginator(
738+
items: $response['products'],
739+
total: $response['total'],
740+
perPage: $recordsPerPage,
741+
currentPage: $page
742+
);
743+
})
744+
->columns([
745+
ImageColumn::make('thumbnail')
746+
->label('Image'),
747+
TextColumn::make('title')
748+
->sortable(),
749+
TextColumn::make('brand')
750+
->state(fn (array $record): string => Str::title($record['brand'] ?? 'Unknown')),
751+
TextColumn::make('category')
752+
->formatStateUsing(fn (string $state): string => Str::headline($state)),
753+
TextColumn::make('price')
754+
->money(),
755+
TextColumn::make('sku')
756+
->label('SKU'),
757+
TextColumn::make('stock')
758+
->label('Stock')
759+
->sortable(),
760+
])
761+
->filters([
762+
SelectFilter::make('category')
763+
->label('Category')
764+
->options(fn (): Collection => Http::baseUrl($baseUrl)
765+
->get('products/categories')
766+
->collect()
767+
->pluck('name', 'slug')
768+
),
769+
])
770+
->searchable();
771+
}
772+
```
773+
774+
<Aside variant="warning">
775+
The [DummyJSON](https://dummyjson.com) API does not support combining sorting, search, and category filtering in a single request.
776+
</Aside>
777+
778+
<Aside variant="info">
779+
The [`select`](https://dummyjson.com/docs/products#products-limit_skip) parameter is used to limit the fields returned by the API. This helps reduce payload size and improves performance when rendering the table.
780+
</Aside>

0 commit comments

Comments
 (0)