diff --git a/packages/tables/docs/09-static-data.md b/packages/tables/docs/09-static-data.md
index d6d7ae7a84..c00fe9d2b1 100644
--- a/packages/tables/docs/09-static-data.md
+++ b/packages/tables/docs/09-static-data.md
@@ -417,3 +417,364 @@ public function table(Table $table): Table
]);
}
```
+
+## Using an external API as a table data source
+
+[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.
+
+### Fetching data from an external API
+
+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):
+
+```php
+use Filament\Tables\Columns\IconColumn;
+use Filament\Tables\Columns\TextColumn;
+use Filament\Tables\Table;
+use Illuminate\Support\Facades\Http;
+
+public function table(Table $table): Table
+{
+ return $table
+ ->records(fn (): array => Http::baseUrl('https://dummyjson.com')
+ ->get('products')
+ ->collect()
+ ->get('products', [])
+ )
+ ->columns([
+ TextColumn::make('title'),
+ TextColumn::make('category'),
+ TextColumn::make('price')
+ ->money(),
+ ]);
+}
+```
+
+`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.
+
+
+
+
+
+#### Setting the state of a column using API data
+
+[Columns](#columns) map to the array keys returned by the `records()` function.
+
+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:
+
+```php
+use Filament\Tables\Columns\TextColumn;
+use Illuminate\Support\Str;
+
+TextColumn::make('category_brand')
+ ->label('Category - Brand')
+ ->state(function (array $record): string {
+ $category = Str::headline($record['category']);
+ $brand = Str::title($record['brand'] ?? 'Unknown');
+
+ return "{$category} - {$brand}";
+ })
+```
+
+
+
+### External API sorting
+
+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.
+
+```php
+use Filament\Tables\Columns\TextColumn;
+use Filament\Tables\Table;
+use Illuminate\Support\Facades\Http;
+
+public function table(Table $table): Table
+{
+ return $table
+ ->records(function (?string $sortColumn, ?string $sortDirection): array {
+ $response = Http::baseUrl('https://dummyjson.com/')
+ ->get('products', [
+ 'sortBy' => $sortColumn,
+ 'order' => $sortDirection,
+ ]);
+
+ return $response
+ ->collect()
+ ->get('products', []);
+ })
+ ->columns([
+ TextColumn::make('title'),
+ TextColumn::make('category')
+ ->sortable(),
+ TextColumn::make('price')
+ ->money(),
+ ]);
+}
+```
+`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.
+
+
+
+
+
+### External API searching
+
+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.
+
+```php
+use Filament\Tables\Columns\TextColumn;
+use Filament\Tables\Table;
+use Illuminate\Support\Facades\Http;
+
+public function table(Table $table): Table
+{
+ return $table
+ ->records(function (?string $search): array {
+ $response = Http::baseUrl('https://dummyjson.com/')
+ ->get('products/search', [
+ 'q' => $search,
+ ]);
+
+ return $response
+ ->collect()
+ ->get('products', []);
+ })
+ ->columns([
+ TextColumn::make('title'),
+ TextColumn::make('category'),
+ TextColumn::make('price')
+ ->money(),
+ ])
+ ->searchable();
+}
+```
+
+`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.
+
+
+
+
+
+### External API filtering
+
+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.
+
+```php
+use Filament\Tables\Columns\TextColumn;
+use Filament\Tables\Filters\SelectFilter;
+use Filament\Tables\Table;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Http;
+
+public function table(Table $table): Table
+{
+ return $table
+ ->records(function (array $filters): array {
+ $category = $filters['category']['value'] ?? null;
+
+ $endpoint = filled($category)
+ ? "products/category/{$category}"
+ : 'products';
+
+ $response = Http::baseUrl('https://dummyjson.com/')
+ ->get($endpoint);
+
+ return $response
+ ->collect()
+ ->get('products', []);
+ })
+ ->columns([
+ TextColumn::make('title'),
+ TextColumn::make('category'),
+ TextColumn::make('price')
+ ->money(),
+ ])
+ ->filters([
+ SelectFilter::make('category')
+ ->label('Category')
+ ->options(fn (): Collection => Http::baseUrl('https://dummyjson.com/')
+ ->get('products/categories')
+ ->collect()
+ ->pluck('name', 'slug')
+ ),
+ ]);
+}
+```
+
+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.
+
+
+
+
+
+### External API pagination
+
+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:
+
+```php
+public function table(Table $table): Table
+{
+ return $table
+ ->records(function (int $page, int $recordsPerPage): LengthAwarePaginator {
+ $skip = ($page - 1) * $recordsPerPage;
+
+ $response = Http::baseUrl('https://dummyjson.com')
+ ->get('products', [
+ 'limit' => $recordsPerPage,
+ 'skip' => $skip,
+ ])
+ ->collect();
+
+ return new LengthAwarePaginator(
+ items: $response['products'],
+ total: $response['total'],
+ perPage: $recordsPerPage,
+ currentPage: $page
+ );
+ })
+ ->columns([
+ TextColumn::make('title'),
+ TextColumn::make('category'),
+ TextColumn::make('price')
+ ->money(),
+ ]);
+}
+```
+
+`$page` and `$recordsPerPage` are automatically injected by Filament based on the current pagination state.
+The calculated `skip` value tells the API how many records to skip before returning results for the current page.
+The response contains `products` (the paginated items) and `total` (the total number of available items).
+These values are passed to a `LengthAwarePaginator`, which Filament uses to render pagination controls correctly.
+
+
+
+### External API full example
+
+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:
+
+- [Search](#external-api-searching) is performed through the `/products/search` endpoint using the `q` parameter.
+- [Category filtering](#external-api-filtering) uses the `/products/category/{category}` endpoint.
+- [Sorting](#external-api-sorting) is handled by sending `sortBy` and `order` parameters to the `/products` endpoint.
+
+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.
+
+```php
+use Filament\Tables\Columns\ImageColumn;
+use Filament\Tables\Columns\TextColumn;
+use Filament\Tables\Filters\SelectFilter;
+use Filament\Tables\Table;
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Str;
+
+public function table(Table $table): Table
+{
+ $baseUrl = 'https://dummyjson.com/';
+
+ return $table
+ ->records(function (
+ ?string $sortColumn,
+ ?string $sortDirection,
+ ?string $search,
+ array $filters,
+ int $page,
+ int $recordsPerPage
+ ) use ($baseUrl): LengthAwarePaginator {
+ // Get the selected category from filters (if any)
+ $category = $filters['category']['value'] ?? null;
+
+ // Choose endpoint depending on search or filter
+ $endpoint = match (true) {
+ filled($search) => 'products/search',
+ filled($category) => "products/category/{$category}",
+ default => 'products',
+ };
+
+ // Determine skip offset
+ $skip = ($page - 1) * $recordsPerPage;
+
+ // Base query parameters for all requests
+ $params = [
+ 'limit' => $recordsPerPage,
+ 'skip' => $skip,
+ 'select' => 'id,title,brand,category,thumbnail,price,sku,stock',
+ ];
+
+ // Add search query if applicable
+ if (filled($search)) {
+ $params['q'] = $search;
+ }
+
+ // Add sorting parameters
+ if ($endpoint === 'products' && $sortColumn) {
+ $params['sortBy'] = $sortColumn;
+ $params['order'] = $sortDirection ?? 'asc';
+ }
+
+ $response = Http::baseUrl($baseUrl)
+ ->get($endpoint, $params)
+ ->collect();
+
+ return new LengthAwarePaginator(
+ items: $response['products'],
+ total: $response['total'],
+ perPage: $recordsPerPage,
+ currentPage: $page
+ );
+ })
+ ->columns([
+ ImageColumn::make('thumbnail')
+ ->label('Image'),
+ TextColumn::make('title')
+ ->sortable(),
+ TextColumn::make('brand')
+ ->state(fn (array $record): string => Str::title($record['brand'] ?? 'Unknown')),
+ TextColumn::make('category')
+ ->formatStateUsing(fn (string $state): string => Str::headline($state)),
+ TextColumn::make('price')
+ ->money(),
+ TextColumn::make('sku')
+ ->label('SKU'),
+ TextColumn::make('stock')
+ ->label('Stock')
+ ->sortable(),
+ ])
+ ->filters([
+ SelectFilter::make('category')
+ ->label('Category')
+ ->options(fn (): Collection => Http::baseUrl($baseUrl)
+ ->get('products/categories')
+ ->collect()
+ ->pluck('name', 'slug')
+ ),
+ ])
+ ->searchable();
+}
+```
+
+
+
+