Grids & Layouts
A grid is the core display unit in Grid Panda. It binds a post query (source_config), a rendering layout and card template (grid_config), and a set of facets into a single embeddable component.
Database Structure
Grids are stored in the wp_gridpanda_grids table:
| Column | Type | Description |
|---|---|---|
| id | bigint unsigned | Auto-increment primary key |
| name | varchar(255) | Human-readable grid name |
| slug | varchar(200) UNIQUE | URL-safe identifier used in [gridpanda_grid slug='...'] |
| layout | varchar(50) | Layout type: grid, masonry, list, carousel, metro, justified |
| source_type | varchar(50) | Type of data source: post_type, taxonomy |
| source_config | longtext (JSON) | Query configuration — post type, status, orderby, etc. |
| card_template | longtext | Card template ID or inline HTML (legacy) |
| grid_config | longtext (JSON) | Layout, pagination, and display configuration |
| created_at / updated_at | datetime | Record timestamps |
source_config — Query Configuration
The source_config JSON defines what posts are queried. Grid Panda passes it to the registered source handler, which converts it into WP_Query arguments:
Post Type Source (most common)
{
"post_type": "product", // Any registered CPT or 'post', 'page'
"post_status": "publish", // publish, draft, pending, private, any
"orderby": "date", // date, title, modified, ID, comment_count,
// menu_order, rand, meta_value, meta_value_num
"order": "DESC", // ASC or DESC
"posts_per_page": 12 // Default items per page (overridden by grid_config)
}Taxonomy Archive Source
{
"post_type": "post",
"taxonomy": "category", // Taxonomy to scope results to
"term_id": 5, // Optional: specific term, null = all terms
"post_status": "publish",
"orderby": "date",
"order": "DESC"
}post_type, taxonomy, user) registered via the gridpanda/grid/register_sources action. Custom source handlers can be added by registering a class that implements the source contract.grid_config — Layout & Display Configuration
The grid_config JSON controls how results are rendered. Full structure:
{
"card_id": 12, // Card template ID
"layout": "grid", // Layout engine to use
"layout_settings": {
"columns": 3, // Desktop columns
"tablet_columns": 2, // Tablet columns
"mobile_columns": 1, // Mobile columns
"gap": "20px", // Gap between items (CSS length)
"row_gap": "20px" // Row gap (defaults to gap)
},
"per_page": 12, // Items per page
"pagination_config": {
"type": "numbered", // numbered | load_more | infinite | none
"position": "bottom", // bottom | top | both
"alignment": "center", // left | center | right
"page_range": 2, // Pages on each side of current
"show_ends": true, // Show first/last page links
"show_results_info": false, // Show "Showing 1–12 of 48"
"scroll_to_top": true, // Scroll to grid on page change
"ajax": true, // AJAX pagination (no full reload)
"infinite_threshold": 300, // px from bottom to trigger load_more/infinite
"labels": {
"prev": "Previous",
"next": "Next",
"load_more": "Load More",
"loading": "Loading…",
"no_more": "No more items",
"results_info": "Showing {from}–{to} of {total}"
}
}
}Pagination Types
| Type | Behaviour | Notes |
|---|---|---|
| numbered | Classic numbered page navigation with prev/next arrows | Works with SEO clean URLs; each page number gets its own URL |
| load_more | "Load More" button appends the next page to the existing results | Results accumulate — no items are removed. Button hides when no more pages. |
| infinite | Automatically loads more results as the user scrolls to the infinite_threshold from the grid bottom | Threshold defaults to 300px above grid bottom |
| none | Renders all matching posts in one pass with no pagination | Use only for small result sets — loads all posts at once |
Layout Types
The layout column determines the layout engine used to position cards. Each layout has its own configuration options within layout_settings:
gridCSS GridFixed-column CSS grid. The most common and performant layout. Columns are set per breakpoint (mobile, tablet, desktop) with configurable gap.
layout_settings keys: columns (int), tablet_columns (int), mobile_columns (int), gap (CSS length), row_gap (CSS length)
masonryMasonryPinterest-style layout where items with variable height are stacked in columns without gaps between them. Uses column-based masonry (CSS columns or JS-calculated positions).
layout_settings keys: columns (int), tablet_columns, mobile_columns, gap (CSS length)
listListSingle-column full-width layout. Each card occupies the full container width. Good for article or blog post listings.
layout_settings keys: gap (CSS length between items)
carouselCarouselHorizontal scroll carousel with previous/next navigation buttons. Shows a configurable number of cards at a time.
layout_settings keys: columns (visible cards), gap, autoplay (bool), autoplay_speed (ms), loop (bool), show_dots (bool), show_arrows (bool)
metroMetroMixed tile sizes using a defined repeating pattern. Some cards are full-width, half-width, or featured size. Pattern cycles through the result set.
layout_settings keys: pattern (array of size definitions), gap
justifiedJustifiedEqual-height rows where each card's width is proportionally adjusted so the row fills the container exactly. Works best with images that have consistent aspect ratios.
layout_settings keys: target_row_height (int px), gap
Query Pipeline
When a grid render is requested (shortcode or REST API), Grid Panda executes the following pipeline:
- 1Resolve source: GridManager reads source_type and source_config, delegates to the matching SourceHandler to produce base WP_Query args.
- 2Apply grid config: Per-page, orderby, and order from grid_config (or request overrides) are merged into the query args.
- 3Inject facet filters: FilterParamHandler reads fx_ query params and calls each active facet type's get_query_args() to build tax_query/meta_query clauses. These are merged into the base args.
- 4Apply gridpanda/grid/query_args filter: Third-party code can modify the final WP_Query args via this filter before execution.
- 5Execute WP_Query: The main query returns the matching post IDs and total count.
- 6Calculate facet counts: For each facet, a complementary query runs against the active result set (excluding that facet's own filter) to compute accurate sibling counts from the index table.
- 7Render cards: Each post is passed through the card template engine (CardBuilder) which resolves dynamic tags and outputs HTML.
- 8Build response: HTML, pagination, facet counts, and metadata are assembled and returned as JSON.
Grid REST API
/wp-json/gridpanda/v1/gridsList all grids
/wp-json/gridpanda/v1/grids/{id}Get a single grid by ID
/wp-json/gridpanda/v1/grids/{id}/renderRender a grid with active facet selections
/wp-json/gridpanda/v1/grids/previewPreview a grid configuration (admin)
/wp-json/gridpanda/v1/gridsCreate a grid (admin)
/wp-json/gridpanda/v1/grids/{id}Update a grid (admin)
/wp-json/gridpanda/v1/grids/{id}Delete a grid (admin)
Render Request & Response
// Request (POST)
POST /wp-json/gridpanda/v1/grids/5/render
{
"facets": {
"color": ["red", "blue"],
"size": ["medium"]
},
"page": 1,
"per_page": 12,
"orderby": "date",
"order": "DESC"
}
// Response
{
"html": "<div class='gridpanda-grid'>…</div>",
"total": 48,
"total_pages": 4,
"page": 1,
"per_page": 12,
"facet_counts": {
"color": { "red": 12, "blue": 9, "green": 5 },
"size": { "small": 8, "medium": 20, "large": 15 }
},
"pagination_html": "<nav class='gridpanda-pagination'>…</nav>"
}Grid Hooks
gridpanda/grid/query_argsfilter$query_args, $gridModify the WP_Query args before grid execution. Use to add custom constraints, change order, or inject additional meta/tax queries.
gridpanda/grid/before_renderaction$grid_id_or_slug, $argsFires before the grid renders. Useful for conditional cache busting or analytics.
gridpanda/grid/after_renderaction$grid_id_or_slug, $html, $queryFires after render with the final HTML and WP_Query object.
gridpanda/grid/htmlfilter$html, $grid, $postsModify the final rendered grid HTML before it is returned.
gridpanda/grid/item_datafilter$item_data, $post, $gridCustomize the data passed to the card template for each post.
gridpanda/grid/dynamic_tagsfilter'', $tag, $post, $is_rawHandle custom dynamic tags not built into Grid Panda. Return non-empty string to handle the tag.
gridpanda/grid/register_sourcesaction$source_managerRegister custom data source handlers.
