Skip to content

Data providers

A grid asks a data provider for rows. The provider is chosen by alias in the definition's dataSource, and the alias is bound to a class in DI. Two things follow: a JSON file can never instantiate an arbitrary class, and the same definition can be pointed at different data by changing one DI binding.

The collection provider

grid.collection is bound to Nebula\Grid\Model\DataProvider\CollectionProvider. It wraps any object that extends Magento\Framework\Data\Collection: plain database collections, EAV collections such as the product collection, and in-memory list collections such as the cache and index management grids.

"dataSource": {
    "provider": "grid.collection",
    "config": {
        "collection": "customer_group.collection",
        "method": "addTaxClass"
    }
}
config key Meaning
collection An alias bound on CollectionRegistry. Required. Sixty-six already ship bound, listed under Collections already bound.
method An argument-less method called on the fresh collection before filters are applied, for a join or a scope the collection offers as a method. The three shipped uses are addTaxClass, addCustomerDetails and useOnlyActual. A method that takes an argument cannot go here. prepareCollection() checks the name with method_exists() and then calls it with no arguments, outside the try/catch that guards the fetch, so "method": "addStoreFilter" raises an uncaught ArgumentCountError rather than rendering an empty grid. Store scoping belongs in a provider that extends CollectionProvider and overrides prepareCollection(), the way the reviews grid adds its per-store visibility join. scope binds a grid to one parent record and is not a store filter.
scope Binds the grid to one parent record: {"field": "<collection field>", "param": "<request param>"}. CollectionProvider reads this key itself and filters the collection on field with the value of the request parameter named by param. It fails closed: with scope declared and no value in the request, the grid renders empty rather than every row. The param is carried on every server-rendered grid link and on the export URL. See the scoped-grid example.

A new collection instance is created per request, because collections carry filter and page state.

Binding a collection

etc/di.xml
<type name="Nebula\Grid\Model\Registry\CollectionRegistry">
    <arguments>
        <argument name="bindings" xsi:type="array">
            <item name="vendor_module_note.collection" xsi:type="string">Vendor\Module\Model\ResourceModel\Note\Collection</item>
        </argument>
    </arguments>
</type>

An alias that is not bound raises UnknownAliasException, which is logged, and the grid renders empty. A class name in the JSON is treated as an alias and fails the same way.

Collections already bound

Sixty-six aliases ship bound on CollectionRegistry, covering most stock entities. Reuse one rather than binding a second alias to the same collection.

The 66 shipped collection aliases

Nebula_Theme

Alias Collection
catalog_rule.collection Magento\CatalogRule\Model\ResourceModel\Rule\Collection
cms_block.collection Magento\Cms\Model\ResourceModel\Block\Collection
cms_page.collection Magento\Cms\Model\ResourceModel\Page\Collection
customer_group.collection Magento\Customer\Model\ResourceModel\Group\Collection
customer_online.collection Magento\Customer\Model\ResourceModel\Online\Grid\Collection
login_as_customer_log.collection Magento\LoginAsCustomerLog\Model\ResourceModel\Log\Collection
email_template.collection Magento\Email\Model\ResourceModel\Template\Collection
inventory_source.collection Magento\Inventory\Model\ResourceModel\Source\Collection
inventory_stock.collection Magento\Inventory\Model\ResourceModel\Stock\Collection
integration.collection Magento\Integration\Model\ResourceModel\Integration\Collection
newsletter_subscriber.collection Magento\Newsletter\Model\ResourceModel\Subscriber\Collection
order_status.collection Magento\Sales\Model\ResourceModel\Order\Status\Collection
product_attributes.collection Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection
sales_order.collection Magento\Sales\Model\ResourceModel\Order\Grid\Collection
sales_order_creditmemo.collection Magento\Sales\Model\ResourceModel\Order\Creditmemo\Grid\Collection
sales_order_invoice.collection Magento\Sales\Model\ResourceModel\Order\Invoice\Grid\Collection
sales_order_shipment.collection Magento\Sales\Model\ResourceModel\Order\Shipment\Grid\Collection
paypal_billing_agreement.collection Magento\Paypal\Model\ResourceModel\Billing\Agreement\Collection
sales_rule.collection Magento\SalesRule\Model\ResourceModel\Rule\Collection
newsletter_queue.collection Magento\Newsletter\Model\ResourceModel\Queue\Grid\Collection
newsletter_template.collection Magento\Newsletter\Model\ResourceModel\Template\Collection
review_product.collection Magento\Review\Model\ResourceModel\Review\Product\Collection
sales_transaction.collection Magento\Sales\Model\ResourceModel\Transaction\Grid\Collection
search_synonyms.collection Magento\Search\Model\ResourceModel\Synonyms\Grid\Collection
search_term.collection Magento\Search\Model\ResourceModel\Query\Collection
sitemap.collection Magento\Sitemap\Model\ResourceModel\Sitemap\Collection
tax_rate.collection Magento\Tax\Model\ResourceModel\Calculation\Rate\Collection
tax_rule.collection Magento\Tax\Model\ResourceModel\Calculation\Rule\Collection
url_rewrite.collection Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection
widget_instance.collection Magento\Widget\Model\ResourceModel\Widget\Instance\Collection

Nebula_Reports

Alias Collection
report_product_viewed.collection Nebula\Reports\Model\ResourceModel\Report\Product\Viewed\Collection
refresh_statistics.collection Magento\Reports\Model\ResourceModel\Refresh\Collection
report_product_lowstock.collection Nebula\Reports\Model\ResourceModel\Product\Lowstock\Collection
report_product_sold.collection Nebula\Reports\Model\ResourceModel\Product\Sold\Collection
report_product_downloads.collection Nebula\Reports\Model\ResourceModel\Product\Downloads\Collection
report_review_customer.collection Magento\Reports\Model\ResourceModel\Review\Customer\Collection
report_review_product.collection Magento\Reports\Model\ResourceModel\Review\Product\Collection
report_review_detail.collection Magento\Reports\Model\ResourceModel\Review\Collection
report_shopcart_product.collection Nebula\Reports\Model\ResourceModel\Shopcart\Product\Collection
report_shopcart_abandoned.collection Nebula\Reports\Model\ResourceModel\Shopcart\Abandoned\Collection
search_term_report.collection Magento\Search\Model\ResourceModel\Query\Collection
report_sales_sales.collection Nebula\Reports\Model\ResourceModel\Sales\Order\Collection
report_sales_tax.collection Nebula\Reports\Model\ResourceModel\Sales\Tax\Collection
report_sales_invoiced.collection Nebula\Reports\Model\ResourceModel\Sales\Invoiced\Collection
report_sales_refunded.collection Nebula\Reports\Model\ResourceModel\Sales\Refunded\Collection
report_sales_shipping.collection Nebula\Reports\Model\ResourceModel\Sales\Shipping\Collection
report_sales_coupons.collection Nebula\Reports\Model\ResourceModel\Sales\Coupons\Collection
report_sales_bestsellers.collection Nebula\Reports\Model\ResourceModel\Sales\Bestsellers\Collection
report_customer_accounts.collection Nebula\Reports\Model\ResourceModel\Customer\Accounts\Collection
report_customer_orders.collection Nebula\Reports\Model\ResourceModel\Customer\Orders\Collection
report_customer_totals.collection Nebula\Reports\Model\ResourceModel\Customer\Totals\Collection
paypal_settlement.collection Magento\Paypal\Model\ResourceModel\Report\Settlement\Row\Collection

Nebula_System

Alias Collection
import_history.collection Magento\ImportExport\Model\ResourceModel\History\Collection
bulk.collection Nebula\System\Model\ResourceModel\Bulk\Grid\Collection
variable.collection Magento\Variable\Model\ResourceModel\Variable\Collection
notification.collection Magento\AdminNotification\Model\ResourceModel\Grid\Collection
cache_types.collection Magento\Backend\Model\Cache\ResourceModel\Grid\Collection
indexer_list.collection Magento\Indexer\Ui\DataProvider\Indexer\DataCollection
backup.collection Magento\Backup\Model\Fs\Collection

Nebula_Store

Alias Collection
design_schedule.collection Magento\Theme\Model\ResourceModel\Design\Collection
design_theme.collection Magento\Theme\Model\ResourceModel\Theme\Grid\Collection
store.collection Magento\Store\Model\ResourceModel\Website\Grid\Collection

Nebula_User

Alias Collection
admin_user.collection Magento\User\Model\ResourceModel\User\Collection
admin_user_locked.collection Magento\User\Model\ResourceModel\User\Locked\Collection
admin_role.collection Magento\Authorization\Model\ResourceModel\Role\Grid\Collection

Nebula_LandingPage

Alias Collection
nebula_landing_page.collection Nebula\LandingPage\Model\ResourceModel\LandingPage\Collection

Nebula_CrudFixture binds cms_block.collection and cms_block.form to its own classes. It is generator output for the generator's tests and is never installed on a store, so those two bindings are not part of the set above. See Modules.

Where a column's data comes from

Every column key is read from the collection's items with getData($key). If the column is not in the collection's select, the cell is empty and a filter on it fails. To add a joined column, add the join in the collection: a plugin on _initSelect(), or an argument-less method in config that performs the join.

What the request looks like

The grid carries these on its own links and on the export URL, and GridDataLoader hands them to the provider under these names:

Parameter Type Meaning
page int 1-based page number
pageSize int rows per page, clamped
sort string column key
sortDir asc or desc the request param is dir on the page and sortDir on the export URL
filters map column key to value; a range or date filter arrives as <key>_from and <key>_to
search string keyword search over searchable columns

GridRequestApplier applies them with three guarantees:

  1. A request can only sort by a column the grid declares. A sort key that is not a declared column is ignored.
  2. A request can only filter on a column the grid declares. The same check, made before any DB identifier is derived from the key.
  3. A page is always bounded. The ceiling is 200 rows for an interactive page and 100,000 for an export. A missing or non-positive pageSize falls back to DEFAULT_PAGE_SIZE, which is 20, on an interactive request and to MAX_EXPORT_PAGE_SIZE, which is 100,000, on an export. It never means "everything".

What applyFilters() does with a declared filter is narrower than the grid UI suggests. It handles two shapes: a select filter as equality, and everything else as LIKE %value%. It does not assemble range or date filters, because price_from is not a declared column and is skipped by the same whitelist. Use declaredFilters() instead, which deliberately keeps the _from and _to keys by matching them against the base column name, then build the ['from' => $from, 'to' => $to] condition yourself and move date bounds from the store timezone to UTC the way CollectionProvider::dayBoundary() does. The generic grid.collection provider already does all of this, so it is only a concern in a provider of your own: a range or date filter left to applyFilters() alone silently does nothing.

Writing your own provider

Implement Nebula\Component\Api\V1\GridDataProviderInterface:

namespace Vendor\Module\Model\DataProvider;

use Nebula\Component\Api\V1\GridDataProviderInterface;
use Nebula\Grid\Model\DataProvider\GridRequestApplier;

class ExternalOrdersProvider implements GridDataProviderInterface
{
    public function __construct(
        private readonly \Vendor\Module\Model\Api\Client $client,
        private readonly GridRequestApplier $applier,
    ) {
    }

    /**
     * @param array<string, mixed> $config   dataSource.config from the definition, plus `columns`
     * @param array<string, mixed> $params   page, pageSize, sort, sortDir, filters, search, and export on an export request
     * @return array{items: array<int, array<string, mixed>>, totalCount: int}
     */
    public function getData(array $config, array $params = []): array
    {
        $columns = $config['columns'] ?? [];
        $export = !empty($params['export']);
        $ceiling = $export ? GridRequestApplier::MAX_EXPORT_PAGE_SIZE : GridRequestApplier::MAX_PAGE_SIZE;

        $pageSize = (int) ($params['pageSize'] ?? 0);
        if ($pageSize <= 0) {
            $pageSize = $export ? $ceiling : GridRequestApplier::DEFAULT_PAGE_SIZE;
        }
        $pageSize = min($pageSize, $ceiling);
        $page = max(1, (int) ($params['page'] ?? 1));

        $sort = (string) ($params['sort'] ?? '');
        if (!isset($columns[$sort]) || empty($columns[$sort]['sortable'])) {
            $sort = 'created_at';
        }

        $result = $this->client->search(
            filters: $this->applier->declaredFilters($params, $columns),
            sort: $sort,
            direction: ($params['sortDir'] ?? 'desc') === 'asc' ? 'asc' : 'desc',
            offset: ($page - 1) * $pageSize,
            limit: $pageSize,
        );

        return ['items' => $result->rows(), 'totalCount' => $result->total()];
    }
}

Return items as a list of associative arrays keyed by column key, and totalCount as the count before paging so the pager is right. The engine passes the definition's columns in config['columns']. GridRequestApplier::declaredFilters() returns only the filters whose keys are declared columns, and its applyPaging(), applySorting(), applyFilters() and applySearch() methods do the rest when your source is a Magento collection, apart from the range and date filters described above. When it is not a collection, keep the three guarantees yourself as above: clamp the page size, whitelist the sort key, and read filters through declaredFilters().

Bind it:

etc/di.xml
<type name="Nebula\Component\Model\Registry\DataProviderRegistry">
    <arguments>
        <argument name="bindings" xsi:type="array">
            <item name="vendor.external_orders" xsi:type="string">Vendor\Module\Model\DataProvider\ExternalOrdersProvider</item>
        </argument>
    </arguments>
</type>

And use it:

"dataSource": { "provider": "vendor.external_orders", "config": { "account": "eu" } }

Anything in config is passed to your provider untouched, so it is the place for provider-specific settings.

Providers already bound

Fifty aliases ship bound on DataProviderRegistry, and between them they back every shipped screen. Check the list before writing a provider: pointing a definition at an alias that already exists costs one line. The .form and .grid suffixes are a naming convention and not a rule the engine enforces.

The 50 shipped provider aliases

Nebula_Grid

Alias Class
grid.collection Nebula\Grid\Model\DataProvider\CollectionProvider
nebula.grid.row Nebula\Grid\Model\InlineEdit\RowFormDataProvider

Nebula_Theme

Alias Class
agreement.grid Nebula\Theme\Model\DataProvider\AgreementGridProvider
attribute.form Nebula\Theme\Model\DataProvider\AttributeFormProvider
attribute_set.grid Nebula\Theme\Model\DataProvider\AttributeSetProvider
catalog_rule.form Nebula\Theme\Model\DataProvider\CatalogRuleProvider
category.form Nebula\Theme\Model\DataProvider\CategoryFormProvider
cms_block.form Nebula\Theme\Model\DataProvider\CmsBlockProvider
cms_page.form Nebula\Theme\Model\DataProvider\CmsPageProvider
customer.form Nebula\Theme\Model\DataProvider\CustomerFormProvider
customer.grid Nebula\Theme\Model\DataProvider\CustomerGridProvider
customer_group.form Nebula\Theme\Model\DataProvider\CustomerGroupProvider
email_template.form Nebula\Theme\Model\DataProvider\EmailTemplateProvider
newsletter_template.form Nebula\Theme\Model\DataProvider\NewsletterTemplateProvider
newsletter_queue.form Nebula\Theme\Model\DataProvider\NewsletterQueueProvider
sitemap.form Nebula\Theme\Model\DataProvider\SitemapProvider
search_term.form Nebula\Theme\Model\DataProvider\SearchTermProvider
order_status.form Nebula\Theme\Model\DataProvider\OrderStatusProvider
sales_order_address.form Nebula\Theme\Model\DataProvider\OrderAddressProvider
inventory_source.form Nebula\Theme\Model\DataProvider\SourceProvider
inventory_stock.form Nebula\Theme\Model\DataProvider\StockProvider
search_synonyms.form Nebula\Theme\Model\DataProvider\SynonymGroupProvider
order_status.grid Nebula\Theme\Model\DataProvider\OrderStatusGridProvider
review.form Nebula\Theme\Model\DataProvider\ReviewFormProvider
checkout_agreement.form Nebula\Theme\Model\DataProvider\CheckoutAgreementProvider
grid.review.collection Nebula\Theme\Model\DataProvider\ReviewCollectionProvider
grid.review.pending.collection Nebula\Theme\Model\DataProvider\PendingReviewCollectionProvider
product.form Nebula\Theme\Model\DataProvider\ProductFormProvider
product.grid Nebula\Theme\Model\DataProvider\ProductProvider
sales_rule.form Nebula\Theme\Model\DataProvider\SalesRuleProvider
sales_rule.coupons.grid Nebula\Theme\Model\DataProvider\SalesRuleCouponsGridProvider
rating.grid Nebula\Theme\Model\DataProvider\RatingGridProvider
review_rating.form Nebula\Theme\Model\DataProvider\RatingProvider
tax_rate.form Nebula\Theme\Model\DataProvider\TaxRateProvider
integration.form Nebula\Theme\Model\DataProvider\IntegrationProvider
tax_rule.form Nebula\Theme\Model\DataProvider\TaxRuleProvider
url_rewrite.form Nebula\Theme\Model\DataProvider\UrlRewriteProvider

Nebula_System

Alias Class
variable.form Nebula\System\Model\DataProvider\VariableProvider
backup.grid Nebula\System\Model\DataProvider\BackupGridProvider

Nebula_Store

Alias Class
design_config.grid Nebula\Store\Model\DataProvider\DesignConfigProvider
design_config.form Nebula\Store\Model\DataProvider\DesignConfigFormProvider
store_group.form Nebula\Store\Model\DataProvider\StoreGroupProvider
store_view.form Nebula\Store\Model\DataProvider\StoreViewProvider
store_website.form Nebula\Store\Model\DataProvider\WebsiteProvider
design_schedule.form Nebula\Store\Model\DataProvider\DesignScheduleProvider
design_theme.form Nebula\Store\Model\DataProvider\ThemeFormProvider

Nebula_User

Alias Class
admin_user.form Nebula\User\Model\DataProvider\AdminUserProvider
admin_role.form Nebula\User\Model\DataProvider\AdminRoleProvider
admin_account.form Nebula\User\Model\DataProvider\AccountProvider

Nebula_LandingPage

Alias Class
nebula_landing_page.form Nebula\LandingPage\Model\DataProvider\LandingPageFormDataProvider

Filter option sources

A select filter takes its options from filterOptions inline, or from a filterOptionsSource alias bound on FilterOptionSourceRegistry. A picker filter takes them from a filterSource alias bound on SelectionSourceRegistry, which searches as the administrator types and is what the product grid's category filter uses. Both are described in the Grid reference.

Events

nebula_grid_data_fetched_after fires after a provider returns, carrying the grid id, the resolved config, the request params, the rows and the total count, so an observer can decorate rows without replacing the provider. It does not fire for exports, which call the provider directly. Payloads are listed in ACL resources and events.