---
title: >-
  Administration: Routing, Data Abstraction Layer, and Data Grid | Shopware
  Community Hub
description: >-
  Learn how to structure administration pages using routing, access, and manage
  data via the Data Abstraction Layer (DAL) and display entities using the Data…
canonical_url: 'https://hub.shopware.com/learn/unit/administration-routing-dal-data-grid'
---

# Administration: Routing, Data Abstraction Layer, and Data Grid

<LearningObjectives>

- Understand how administration routing connects module routes, page components, and navigation entries.
- Learn how to work with DAL repositories, `Criteria`, and the Admin API to load and change entity data.
- Understand how entity data moves into component state and is rendered in the UI with the Data Grid component.
- Recognize the basic error-handling pattern for write operations and where advanced field-level validation would continue.

</LearningObjectives>

# Administration: Routing, Data Abstraction Layer, and Data Grid

The Shopware administration is more than just a user interface. It is a powerful frontend application that allows you to build custom features for merchants and internal users.

You will learn how administration features are accessed and navigated, how to work with the Data Abstraction Layer (DAL) via the Admin API, and how to present data using the Data Grid component.

## Routing

Administration routing defines **where a feature lives** inside the Shopware administration. It connects a URL with a page component and provides the entry point for administration features.

In the previous practical lab, you already worked with routing, even if you didn't define them from scratch. Let's dive deeper into routing and learn how to define custom routes for modules.

### Administration Routing vs. Storefront Routing

Routing in the administration follows a different goal than routing in the storefront.

- Storefront routing focuses on **customer-facing pages** and **SEO-relevant URLs**.
- Administration routing focuses on **feature entry points** and **navigation context**.

In the administration, routes are not just URLs. They define pages and features.

### Defining a Custom Administration Route

Administration routes are defined inside a module using the `routes` property. Each route links a **path** to a **component** that should be rendered when the user navigates to that route. The full route name is composed of the module name and the route key you define.

Here is a minimal isolated example of a custom administration module with one route:

```js
// [plugin_root]/src/Resources/app/administration/src/module/my-example-module/index.js
Shopware.Module.register('my-example-module', {
  routes: {
    overview: {
      component: 'my-example-module-overview',
      path: 'my-custom-path'
    }
  }
});
```

In the browser, it is accessible at: `/admin#/my-example-module/my-custom-path`. When running Shopware locally, the full URL would be: `http://localhost:8000/admin#/my-example-module/my-custom-path`.

The `path` defines the URL segment inside the administration. And the `component` defines the component that should be rendered when the user navigates to that route.

This is almost the same setup you saw in the previous practical lab. There, you registered a custom component and defined a route that points to it.

<Callout title="Note" type="info">

In the previous practical lab, you **added a tab to an existing module**. To do so, you added a custom route to an existing module and pushed it into the `children` property.

If you want to dive deeper into **adding a tab to an existing module**, take a look at the [official documentation](https://developer.shopware.com/docs/guides/plugins/plugins/administration/routing-navigation/add-new-tab.html).

</Callout>

### Adding a Navigation Menu Entry

Administration navigation menu entries allow users to open routes via the administration sidebar. They are defined using the `navigation` property inside your module definition. It points to an **existing route**.

Example:

```js
// [plugin_root]/src/Resources/app/administration/src/module/my-example-module/index.js
Shopware.Module.register('my-example-module', {
  routes: {
    overview: {
      component: 'my-example-module-overview',
      path: 'my-custom-path'
    }
  },
  navigation: [{
    label: 'My example page',
    path: 'my.example.module.overview',
    parent: 'sw-customer',
    position: 100
  }]
});
```

**In this example:**

- The **route** defines the page (`overview`)
- The **navigation entry** adds menu item to the administration sidebar (`My example page`)
- The `path` of the navigation entry references the route (`my.example.module.overview`)
- The `parent` defines where the menu item appears (here: under **Customers**)
- The `position` property defines the position of the menu item (`100`)

The navigation entry does not create a page. It only provides a shortcut to an existing route.

### Why Routes and Navigation Entries are Separated

Routes and navigation entries are intentionally separated. This allows you to:

- Create routes without exposing them in the menu.
- Reuse routes in different navigation contexts.
- Extend existing administration areas without changing their core structure.

Understanding this separation is important when building custom administration features.

If you want to explore this topic further, take a look at the [official documentation](https://developer.shopware.com/docs/guides/plugins/plugins/administration/routing-navigation/add-menu-entry.html).

<Callout title="Important Note" type="info">

Navigation entries **cannot be added on the first navigation level** (e.g., orders, catalog, customers, etc.). This is an intentional design decision; allowing plugins to add items on the first level would quickly overload the main navigation.

Custom navigation entries must always be added **below an existing parent entry**.

</Callout>

## Data Abstraction Layer (DAL) in the Administration

In the Shopware administration, reading and writing data is done through the Data Abstraction Layer (DAL) on the client side.

Instead of accessing the database directly, administration components communicate with the backend via the Admin API using the DAL as an abstraction layer between the administration and the backend.

The DAL provides a repository-based pattern that allows you to work with data as entities in a consistent, safe, and structured way. You don't need to write HTTP requests manually, as repositories handle the communication with the backend for you.

This DAL approach is very similar to how the DAL is used in the backend (PHP) plugins but executed from the client side (Vue.js).

As a result, Shopware provides a **single source of truth** for data handling across the administration and the backend.

### Data Handling: The Mental Model

When you work with entities in the administration, you are essentially using the DAL **remotely**:

- Your Vue component uses a `Repository` created by `repositoryFactory`.
- The repository sends requests to the **Admin API**.
- The API returns entities (single `Entity` or `EntityCollection`) and metadata (e.g., `total`).

### Relevant Building Blocks

The most important building blocks in the administration mirror the backend DAL concepts:

- **`repositoryFactory`**: creates repositories for entities (`product`, `customer`, etc.).
- **`Repository`**: provides CRUD methods (`search`, `get`, `save`, `delete`, `create`).
- **`Shopware.Context.api`**: defines the current API context (e.g., language, permission, user).
- **`Criteria`**: The criteria object is used for pagination, sorting, filters, term search, and associations. This means you define exactly which data you want to fetch by configuring the criteria object.

### RepositoryFactory

Repositories are provided via the administration DI container. The typical pattern is:

- Inject `repositoryFactory` into the component.
- Create a repository for a specific entity (e.g. `product`, `customer`, `order`, etc.).

```js
export default {
    inject: ['repositoryFactory'],

    computed: {
        productRepository() {
            return this.repositoryFactory.create('product');
        },
    },
};
```

<Callout title="Important: The create() Method Has Two Different Meanings" type="warning">

In the administration DAL, the method `create()` is used in two different contexts.

1. The `repositoryFactory.create('<entity>')` creates a **repository instance** and not an entity.
   This is used to access CRUD methods such as `search()`, `get()`, `save()`, and `delete()` for a specific entity type (e.g., `product`, `customer`, `order`, etc.`).

2. The `productRepository.create(Shopware.Context.api)` creates a **new entity instance** in memory, and not a repository.
   This only creates the entity object locally. It is saved to the backend **only after** you call the `save(entity, Shopware.Context.api)` method.

Always pay attention to which object you are calling `create()` on.

</Callout>

### The Criteria Object

The `Criteria` object is the central object for building queries when working with the DAL in the administration. It allows you to define exactly which data you want to load. It can define:

- Pagination: `new Criteria(page, limit)`
- Sorting: `criteria.addSorting(Criteria.sort('product.name', 'ASC'))`
- Filters: `criteria.addFilter(Criteria.equals('product.active', true))`
- Associations: `criteria.addAssociation('manufacturer')`

A simple criteria example that loads the first 25 products:

```js
const { Criteria } = Shopware.Data;

const criteria = new Criteria(1, 25);
```

Often you need to refine your criteria to match your specific requirements.

**Example: Sorting**

```js
criteria.addSorting(Criteria.sort('product.name', 'ASC'));
```

**Example: Filtering**

```js
criteria.addFilter(Criteria.equals('product.active', true));
```

**Associations:**

By default, related entities (such as `manufacturer` or `categories`) are not loaded automatically. If you need related data, you must explicitly request it via `addAssociation`.

For example, you created a `productRepository` and your specific requirement is to also load related data such as the manufacturer and categories. To load this data along with the products, add the association to the criteria:

```js
const criteria = new Criteria(1, 25);
criteria.addAssociation('manufacturer');
criteria.addAssociation('categories'); // you can request multiple associations
```

The rule of thumb is: **Always add associations when you need related data**.

### Read Data

#### Using the Search method

To load data from the backend, use the `search` method on the repository.

```js
const { Criteria } = Shopware.Data;

export default {
  inject: ['repositoryFactory'],

  computed: {
    productRepository() {
      return this.repositoryFactory.create('product');
    },
  },
  methods: {
    async fetchProducts() {
      const criteria = new Criteria(1, 25);
      return await this.productRepository.search(criteria, Shopware.Context.api);
    }
  }
};
```

The `search` method returns an `EntityCollection` which contains the fetched entities.

#### Using the Get Method

To load a single entity instance instead of a collection, use the `get` method on the repository.

```js
export default {
  inject: ['repositoryFactory'],

  data() {
    return {
      product: null
    };
  },
  
  computed: {
    productRepository() {
      return this.repositoryFactory.create('product');
    },
  },
  methods: {
    async getProductById(productId) {
      this.product = await this.productRepository.get(productId, Shopware.Context.api);
    }
  }
};
```

### Write Data

Repositories also support write operations such as `create`, `save`, and `delete`. These operations persist changes.

```js
export default {
  inject: ['repositoryFactory'],

  data() {
    return {
      product: null
    };
  },

  computed: {
    productRepository() {
      return this.repositoryFactory.create('product');
    },
  },

  methods: {
    async updateProductName(productId) {
      // Load single product entity by ID
      this.product = await this.productRepository.get(productId, Shopware.Context.api);
      
      // Modify the product entity and save it
      this.product.name = 'New product name';
      await this.productRepository.save(this.product, Shopware.Context.api);
      
      // Optional: Re-Fetch for a clean, up-to-date UI state
      this.product = await this.productRepository.get(productId, Shopware.Context.api);
    }
  }
};
```

In practice, write operations are usually followed by a reload to ensure the UI reflects the current state of the backend.

### Error Handling (Validation Errors)

When a `save()` fails, it is commonly due to validation errors (required fields missing, invalid formats, permissions). Treat it like any async operation:

- Wrap `save()` in `try/catch`
- Show a notification
- Optionally, reload the entity/list to reset the inconsistent UI state

```js
try {
    await this.productRepository.save(this.product, Shopware.Context.api);
    this.createNotificationSuccess({ title: this.$t('global.default.success') });
} catch (e) {
    this.createNotificationError({ title: this.$t('global.default.error') });
}
```

The `catch` block can also contain the API response, for example in `e.response` or `e.response.data.errors`. In a real form, you could use that response to store messages in the component state, such as `this.errorMessages`, or to show a more specific notification.

### Mental Model

When working with data in the administration, the basic flow is always the same:

1. Use a DAL repository to fetch or modify entities (via the Admin API).
2. Store entities in the component state.
3. Reflect the current state in the UI.
4. Re-fetch data after write-operations to ensure a consistent UI state.

## Data Grid

The Shopware administration provides a reusable table component called `sw-data-grid`. It is used throughout the administration whenever tabular data needs to be displayed and managed.

From a frontend perspective, the data grid is responsible for **presenting data**, while data loading and manipulation are handled via the DAL.

A data grid consists of two essential parts:

- The `data-source`: An array of row objects.
- The `columns`: An array of column definitions that map to properties of the row objects.

<Callout title="Note" type="info">

In many core administration listings, Shopware uses the `sw-entity-listing`, which wraps the data grid and provides common listing behavior.

In this learning unit, the focus is on `sw-data-grid` to understand the underlying mechanics.

</Callout>

### Minimal Example (Static Data)

Before loading entities via the DAL, it is useful to start with a static example to understand how the data grid maps rows to columns.

**Template:**

```twig
{# [plugin_root]/src/Resources/app/administration/src/module/my-example-module/page/my-example-module-overview/my-example-module-overview.html.twig #}
<sw-data-grid
    :data-source="rows"
    :columns="columns"
    :show-selection="false"
    :show-actions="false"
/>
```

As you can see, we give the grid a `data-source` and `columns` property. These properties are bound to data properties defined in the component.

The names (e.g., `rows` and `columns`) are not fixed and can be chosen freely. What matters is that the names used in the template match the properties defined in the component.

If you rename `rows` or `columns` in the component, you must update the bindings in the template accordingly.

**Component:**

```js
// [plugin_root]/src/Resources/app/administration/src/module/my-example-module/page/my-example-module-overview/index.js
import template from './my-example-module-overview.html.twig';

export default {
    template,

    data() {
        return {
            rows: [
                { id: '1', name: 'Portia Jobson', company: 'Wordify' },
                { id: '2', name: 'Baxy Eardley', company: 'Twitternation' },
            ],
            columns: [
                { property: 'name', label: 'Name' },
                { property: 'company', label: 'Company' },
            ],
        };
    },
};
```

In this example:

- Each object in `rows` represents a single row.
- Each column maps its `property` to a field on the row object.
- The grid renders the data without any backend interaction.

### Custom Cell Rendering With Slots

If you want to customize the rendering of a specific column, you can use the `column-<property>` slot.

**Example:**

```twig
<sw-data-grid
    :data-source="items"
    :columns="columns"
    :show-selection="false"
    :show-actions="false"
>
    <template #column-name="{ item }">
        <strong>{{ item.name }}</strong>
    </template>
</sw-data-grid>
```

Slots are commonly used to:

- Format values
- Display icons and badges
- Render links or custom UI elements

### Data Handling With DAL (Repository + Criteria)

In real administration modules, tabular data is usually loaded via the DAL. This means the data grid can be filled with data using the DAL repositories.

The most important blocks are:

- `repositoryFactory.create('<entity>')` to create a repository instance.
- `Shopware.Data.Criteria` to define search criteria.
- `repository.search(criteria, Shopware.Context.api)` to fetch entities.

You may notice that this principle is very similar to working with the DAL in the backend (PHP) of a Shopware plugin. The main difference is that in the administration the data is fetched on the client side and rendered in the administration UI.

### Example: Load Products and Render Them in a Data Grid

This example shows how to load product entities via the DAL and display them in a data grid:

```js
// [plugin_root]/src/Resources/app/administration/src/module/my-example-module/page/my-example-module-overview/index.js
import template from './my-example-module-overview.html.twig';

const { Criteria } = Shopware.Data;

export default {
    template,

    inject: ['repositoryFactory'],

    data() {
        return {
            isLoading: false,
            products: [],
            columns: [
                { property: 'name', label: 'Name', routerLink: 'sw.product.detail' },
                { property: 'productNumber', label: 'Product number' },
                { property: 'active', label: 'Active', inlineEdit: 'boolean' },
            ],
        };
    },

    computed: {
        productRepository() {
            return this.repositoryFactory.create('product');
        },

        criteria() {
            return new Criteria(1, 25);
        },
    },

    created() {
        this.loadProducts();
    },

    methods: {
      async loadProducts() {
        this.isLoading = true;

        try {
          this.products = await this.productRepository.search(
            this.criteria,
            Shopware.Context.api
          );
        } finally {
          this.isLoading = false;
        }
      },
    },
};
```

And the template can look like this:

```twig
<sw-data-grid
    :data-source="products"
    :columns="columns"
    :is-loading="isLoading"
    :show-selection="false"
    :show-actions="false"
/>
```

**In this example:**

- The `repositoryFactory` is injected so the component can communicate with the DAL with the Shopware Admin API.
- A repository for the `product` entity is created using `repositoryFactory.create('product')`.
- The `Criteria` defines how many products (entities) should be loaded (in this case the first 25 products).
- When the component is created, the method `loadProducts` is called automatically.
- Inside `loadProducts`, the products are fetched from the repository using `productRepository.search(criteria, Shopware.Context.api)`.
  Internally, this triggers an Admin API request to the Shopware backend, which returns the matching product data.
- The fetched products are stored in the `products` data property.
- The data grid receives the `products` array and displays each product as a table row.
- While the grid is loading, the grid shows a loading state using the `isLoading` property.

As a result, the data grid is automatically filled with product data as soon as the page is opened.

**Mental Model:**

When working with data in the administration, the basic flow is always the same:

1. Use a DAL repository to fetch entities from the backend.
2. Store the fetched entities (the result) in the component state (component properties).
3. Render the data in the UI using the `sw-data-grid`.

## Summary

Well done! In this learning unit, you learned:

- How administration routing works and how to define custom routes and navigation entries.
- How the Data Abstraction Layer (DAL) is used in the administration to read and write data via repositories.
- That the Admin API is used to communicate with the Shopware backend.
- How to build queries using the `Criteria` object to control pagination, sorting, filters, and associations.
- How to display and manage tabular data using the `sw-data-grid` component.
- How to combine DAL data handling with UI components to build real administration features.

With this knowledge, you can build custom administration modules that load, display and manage Shopware entities in a structured, maintainable, and scalable way.
