Page Blocks
In the Dashboard, all pages are built from blocks. Every block has a pageId and a blockId which uniquely locates it in the
app (see Dev Mode section).
You can also define your own blocks, which can be added to any page and can even replace the default blocks.
All available options are documented in the DashboardPageBlockDefinition reference
Basic Page Block Example
Here's an example of how to define a custom page block:
import { defineDashboardExtension } from '@vendure/dashboard';
defineDashboardExtension({
    pageBlocks: [
        {
            id: 'related-articles',
            title: 'Related Articles',
            location: {
                // This is the pageId of the page where this block will be
                pageId: 'product-detail',
                // can be "main" or "side"
                column: 'side',
                position: {
                    // Blocks are positioned relative to existing blocks on
                    // the page.
                    blockId: 'facet-values',
                    // Can be "before", "after" or "replace"
                    // Here we'll place it after the `facet-values` block.
                    order: 'after',
                },
            },
            component: ({ context }) => {
                // In the component, you can use the `context` prop to
                // access the entity and the form instance.
                return <div className="text-sm">Articles related to {context.entity.name}</div>;
            },
        },
    ],
});
This will add a "Related Articles" block to the product detail page:

Block Positioning
Page blocks can be positioned in three ways relative to existing blocks:
Before
Places the block before the specified blockId:
position: {
    blockId: 'product-variants',
    order: 'before'
}
After
Places the block after the specified blockId:
position: {
    blockId: 'product-variants',
    order: 'after'
}
Replace
Replaces the existing block entirely:
position: {
    blockId: 'product-variants',
    order: 'replace'
}
Block Columns
Blocks can be placed in two columns:
main: The main content area (wider column on the left)side: The sidebar area (narrower column on the right)
Context Data
The context prop provides access to:
entity: The current entity being viewed/edited (e.g., Product, Customer, etc.)form: The React Hook Form instance for the current page (if applicable)route: Route information and parameters
Block Visibility
The visibility of a block can be dynamically controlled using the shouldRender function. This function receives the same
context object as the block component, and should return a boolean to determine whether the block should be rendered.
import { defineDashboardExtension } from '@vendure/dashboard';
import { AdvancedTaxInfo } from './advanced-tax-info.tsx';
defineDashboardExtension({
    pageBlocks: [
        {
            id: 'advanced-tax-info',
            location: {
                pageId: 'product-variant-detail',
                column: 'side',
                position: {
                    blockId: 'facet-values',
                    order: 'after',
                },
            },
            component: AdvancedTaxInfo,
            shouldRender: context => {
                // You can use custom and build-in hooks
                // in this function
                const { activeChannel } = useChannel();
                const hasTaxSettings = context.entity?.customFields?.taxSettings
                // This block will only render if the entity has the
                // expected custom field data, and the active channel has
                // the given tax setting
                return hasTaxSettings && activeChannel.pricesIncludeTax === false;
            },
        },
    ],
});
The shouldRender function can be used to hide built-in blocks by combining it with the "replace" position
on an existing blockId.
Advanced Example
Here's a more complex example that shows different types of blocks:
import { defineDashboardExtension, Button } from '@vendure/dashboard';
import { useState } from 'react';
defineDashboardExtension({
    pageBlocks: [
        // Analytics block for product page
        {
            id: 'product-analytics',
            title: 'Product Analytics',
            location: {
                pageId: 'product-detail',
                column: 'side',
                position: {
                    blockId: 'product-assets',
                    order: 'after',
                },
            },
            component: ({ context }) => {
                const product = context.entity;
                return (
                    <div className="space-y-2">
                        <div className="text-sm text-muted-foreground">Last 30 days</div>
                        <div className="grid grid-cols-2 gap-4">
                            <div>
                                <div className="text-2xl font-bold">142</div>
                                <div className="text-xs text-muted-foreground">Views</div>
                            </div>
                            <div>
                                <div className="text-2xl font-bold">23</div>
                                <div className="text-xs text-muted-foreground">Orders</div>
                            </div>
                        </div>
                    </div>
                );
            },
        },
        // Quick actions block
        {
            id: 'quick-actions',
            title: 'Quick Actions',
            location: {
                pageId: 'product-detail',
                column: 'main',
                position: {
                    blockId: 'product-variants',
                    order: 'after',
                },
            },
            component: ({ context }) => {
                const [isProcessing, setIsProcessing] = useState(false);
                const handleSync = async () => {
                    setIsProcessing(true);
                    // Simulate API call
                    await new Promise(resolve => setTimeout(resolve, 2000));
                    setIsProcessing(false);
                };
                return (
                    <div className="flex gap-2">
                        <Button variant="outline" onClick={handleSync} disabled={isProcessing}>
                            {isProcessing ? 'Syncing...' : 'Sync to External System'}
                        </Button>
                        <Button variant="outline">Generate QR Code</Button>
                    </div>
                );
            },
        },
    ],
});
Finding Block IDs
To find the pageId and blockId values for positioning your blocks:
- Enable Dev Mode in the dashboard
 - Navigate to the page where you want to add your block
 - Hover over existing blocks to see their IDs
 - Use these IDs in your block positioning configuration
 
- Use descriptive IDs: Choose clear, unique IDs for your blocks
 - Position thoughtfully: Consider the user experience when placing blocks
 - Handle loading states: Show appropriate loading indicators for async operations
 - Follow design patterns: Use the dashboard's existing UI components for consistency
 - Test thoroughly: Verify your blocks work correctly on different screen sizes