survos / api-grid-bundle
incorporate DatatablesNet, using twig and stimulus, twig_component requires ^2.2
Package info
github.com/survos/api-grid-bundle
Type:symfony-bundle
pkg:composer/survos/api-grid-bundle
Fund package maintenance!
Requires
- php: ^8.5
- api-platform/symfony: ^4.1 || ^5.0
- doctrine/doctrine-bundle: ^3.0
- doctrine/orm: ^3.3
- doctrine/persistence: ^4.1
- survos/field-bundle: ^2.5
- survos/grid-bundle: ^2.5
- survos/js-twig-bundle: ^2.5
- survos/kit-bundle: ^2.5
- symfony/config: ^7.4||^8.1
- symfony/dependency-injection: ^7.4||^8.1
- symfony/dom-crawler: ^7.4||^8.1
- symfony/http-kernel: ^7.4||^8.1
- symfony/options-resolver: ^7.4||^8.1
- symfony/ux-twig-component: ^3
- twig/twig: ^3.4|^4.0
Requires (Dev)
- api-platform/doctrine-orm: ^4.1 || ^5.0
- meilisearch/meilisearch-php: ^1.11
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.0
- rector/rector: ^2.0
- symfony/browser-kit: ^7.4||^8.1
- symfony/framework-bundle: ^7.4||^8.1
- symfony/http-client: ^7.4||^8.1
- symfony/intl: ^7.4||^8.1
- symfony/phpunit-bridge: ^7.4||^8.1
- symfony/stopwatch: ^7.4||^8.1
- symfony/twig-bundle: ^7.4||^8.1
- symfony/ux-chartjs: ^3
- symfony/var-dumper: ^7.4||^8.1
- symfony/yaml: ^7.4||^8.1
- zenstruck/class-metadata: ^1.1
Suggests
- api-platform/doctrine-common: doctrine integration
- api-platform/symfony: For javascript-based grid rendering
- monolog/monolog: Allows more advanced logging of the application flow
Provides
None
Conflicts
None
Replaces
None
- 2.34.38
- 2.34.32
- 2.34.29
- 2.34.21
- 2.34.20
- 2.33.0
- 2.31.5
- 2.31.1
- 2.29.1
- 2.19.2
- 2.19.0
- 2.18.19
- 2.18.18
- 2.15.22
- 2.15.3
- 2.10.19
- 2.10.18
- 2.10.17
- 2.10.16
- 2.10.15
- 2.10.14
- 2.10.13
- 2.10.12
- 2.10.11
- 2.10.10
- 2.10.9
- 2.10.8
- 2.10.7
- 2.10.6
- 2.10.5
- 2.10.4
- 2.10.3
- 2.10.2
- 2.10.1
- 2.10.0
- 2.9.4
- 2.9.3
- 2.9.2
- 2.9.1
- 2.9.0
- 2.8.4
- 2.8.2
- 2.8.1
- 2.8.0
- 2.7.23
- 2.7.22
- 2.7.21
- 2.7.20
- 2.7.19
- 2.7.18
- 2.7.17
- 2.7.16
- 2.7.15
- 2.7.14
- 2.7.13
- 2.7.12
- 2.7.11
- 2.7.10
- 2.7.9
- 2.7.8
- 2.7.7
- 2.7.6
- 2.7.5
- 2.7.4
- 2.7.3
- 2.7.2
- 2.7.1
- 2.7.0
- 2.6.0
- 2.5.8
- 2.5.7
- 2.5.6
- 2.5.5
- 2.5.3
- 2.5.2
- 2.5.1
- 2.5.0
- 2.4.4
- 2.4.3
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.0
- 2.2.5
- 2.2.4
- 2.2.3
- 2.2.2
- 2.2.1
- 2.2.0
- dev-main / 2.1.x-dev
- 2.1.2
- 2.1.1
- 2.0.220
- 2.0.219
- 2.0.218
- 2.0.217
- 2.0.216
- 2.0.215
- 2.0.214
- 2.0.213
- 2.0.212
- 2.0.211
- 2.0.210
- 2.0.209
- 2.0.208
- 2.0.207
- 2.0.206
- 2.0.205
- 2.0.204
- 2.0.203
- 2.0.202
- 2.0.201
- 2.0.200
- 2.0.199
- 2.0.198
- 2.0.197
- 2.0.196
- 2.0.195
- 2.0.194
- 2.0.193
- 2.0.192
- 2.0.191
- 2.0.190
- 2.0.189
- 2.0.188
- 2.0.187
- 2.0.186
- 2.0.185
- 2.0.184
- 2.0.183
- 2.0.182
- 2.0.181
- 2.0.180
- 2.0.179
- 2.0.178
- 2.0.177
- 2.0.176
- 2.0.175
- 2.0.174
- 2.0.173
- 2.0.172
- 2.0.171
- 2.0.170
- 2.0.169
- 2.0.168
- 2.0.167
- 2.0.166
- 2.0.165
- 2.0.164
- 2.0.163
- 2.0.162
- 2.0.161
- 2.0.160
- 2.0.159
- 2.0.158
- 2.0.156
- 2.0.155
- 2.0.154
- 2.0.146
- 2.0.145
- 2.0.144
- 2.0.143
- 2.0.142
- 2.0.141
- 2.0.140
- 2.0.139
- 2.0.138
- 2.0.137
- 2.0.136
- 2.0.135
- 2.0.134
- 2.0.133
- 2.0.132
- 2.0.131
- 2.0.130
- 2.0.129
- 2.0.128
- 2.0.127
- 2.0.126
- 2.0.125
- 2.0.124
- 2.0.123
- 2.0.122
- 2.0.121
- 2.0.120
- 2.0.119
- 2.0.117
- 2.0.116
- 2.0.115
- 2.0.114
- 2.0.113
- 2.0.112
- 2.0.111
- 2.0.110
- 1.6.44
- 1.6.43
- 1.6.42
- 1.6.41
- 1.6.40
- 1.6.39
- 1.6.38
- 1.6.37
- 1.6.36
- 1.6.35
- 1.6.34
- 1.6.33
- 1.6.32
- 1.6.31
- 1.6.30
- 1.6.29
- 1.6.28
- 1.6.27
- 1.6.26
- 1.6.25
- 1.6.24
- 1.6.23
- 1.6.22
- 1.6.21
- 1.6.20
- 1.6.19
- 1.6.18
- 1.6.17
- 1.6.16
- 1.6.15
- 1.6.14
- 1.6.13
- 1.6.12
- 1.6.11
- 1.6.10
- 1.6.9
- 1.6.8
- 1.6.7
- 1.6.6
- 1.6.5
- 1.6.4
- 1.6.3
- 1.6.2
- 1.6.1
- 1.6.0
- 1.5.600
- 1.5.529
- 1.5.528
- 1.5.527
- 1.5.526
- 1.5.525
- 1.5.524
- 1.5.523
- 1.5.522
- 1.5.521
- 1.5.520
- 1.5.519
- 1.5.518
- 1.5.517
- 1.5.516
- 1.5.515
- 1.5.514
- 1.5.513
- 1.5.512
- 1.5.511
- 1.5.510
- 1.5.509
- 1.5.508
- 1.5.507
- 1.5.506
- 1.5.505
- 1.5.504
- 1.5.503
- 1.5.502
- 1.5.501
- 1.5.500
- 1.5.499
- 1.5.498
- 1.5.497
- 1.5.496
- 1.5.495
- 1.5.494
- 1.5.493
- 1.5.492
- 1.5.491
- 1.5.490
- 1.5.489
- 1.5.488
- 1.5.487
- 1.5.486
- 1.5.485
- 1.5.484
- 1.5.483
- 1.5.482
- 1.5.481
- 1.5.480
- 1.5.479
- 1.5.478
- 1.5.477
- 1.5.476
- 1.5.475
- 1.5.474
- 1.5.473
- 1.5.472
- 1.5.471
- 1.5.470
- 1.5.469
- 1.5.468
- 1.5.467
- 1.5.466
- 1.5.465
- 1.5.464
- 1.5.463
- 1.5.462
- 1.5.461
- 1.5.460
- 1.5.459
- 1.5.458
- 1.5.457
- 1.5.456
- 1.5.455
- 1.5.454
- 1.5.453
- 1.5.452
- 1.5.451
- 1.5.450
- 1.5.449
- 1.5.448
- 1.5.447
- 1.5.446
- 1.5.445
- 1.5.444
- 1.5.443
- 1.5.442
- 1.5.441
- 1.5.440
- 1.5.439
- 1.5.438
- 1.5.437
- 1.5.436
- 1.5.435
- 1.5.434
- 1.5.433
- 1.5.432
- 1.5.431
- 1.5.430
- 1.5.429
- 1.5.428
- 1.5.427
- 1.5.426
- 1.5.425
- 1.5.424
- 1.5.423
- 1.5.422
- 1.5.421
- 1.5.420
- 1.5.419
- 1.5.418
- 1.5.417
- 1.5.416
- 1.5.415
- 1.5.414
- 1.5.413
- 1.5.412
- 1.5.411
- 1.5.410
- 1.5.409
- 1.5.408
- 1.5.407
- 1.5.406
- 1.5.405
- 1.5.404
- 1.5.403
- 1.5.402
- 1.5.401
- 1.5.400
- 1.5.399
- 1.5.398
- 1.5.397
- 1.5.396
- 1.5.395
- 1.5.394
- 1.5.393
- 1.5.392
- 1.5.391
- 1.5.390
- 1.5.389
- 1.5.388
- 1.5.387
- 1.5.386
- 1.5.385
- 1.5.384
- 1.5.383
- 1.5.382
- 1.5.381
- 1.5.380
- 1.5.379
- 1.5.378
- 1.5.377
- 1.5.376
- 1.5.375
- 1.5.374
- 1.5.373
- 1.5.372
- 1.5.371
- 1.5.370
- 1.5.369
- 1.5.368
- 1.5.367
- 1.5.366
- 1.5.365
- 1.5.364
- 1.5.363
- 1.5.362
- 1.5.361
- 1.5.360
- 1.5.359
- 1.5.358
- 1.5.357
- 1.5.356
- 1.5.355
- 1.5.354
- 1.5.353
- 1.5.352
- 1.5.351
- 1.5.350
- 1.5.349
- 1.5.345
- 1.5.344
- 1.5.343
- 1.5.342
- 1.5.341
- 1.5.340
- 1.5.339
- 1.5.338
- 1.5.337
- 1.5.336
- 1.5.335
- 1.5.334
- 1.5.333
- 1.5.332
- 1.5.331
- 1.5.330
- 1.5.329
- 1.5.328
- 1.5.327
- 1.5.326
- 1.5.325
- 1.5.324
- 1.5.323
- 1.5.322
- 1.5.321
- 1.5.320
- 1.5.319
- 1.5.318
- 1.5.317
- 1.5.316
- 1.5.315
- 1.5.314
- 1.5.313
- 1.5.312
- 1.5.311
- 1.5.310
- 1.5.309
- 1.5.308
- 1.5.307
- 1.5.306
- 1.5.305
- 1.5.304
- 1.5.303
- 1.5.302
- 1.5.301
- 1.5.300
- 1.5.299
- 1.5.298
- 1.5.297
- 1.5.296
- 1.5.295
- 1.5.294
- 1.5.293
- 1.5.292
- 1.5.291
- 1.5.290
- 1.5.289
- 1.5.288
- 1.5.287
- 1.5.286
- 1.5.285
- 1.5.284
- 1.5.283
- 1.5.282
- 1.5.281
- 1.5.280
- 1.5.279
- 1.5.278
- 1.5.277
- 1.5.276
- 1.5.275
- 1.5.274
- 1.5.273
- 1.5.272
- 1.5.271
- 1.5.270
- 1.5.269
- 1.5.268
- 1.5.267
- 1.5.266
- 1.5.265
- 1.5.264
- 1.5.263
- 1.5.262
- 1.5.261
- 1.5.260
- 1.5.259
- 1.5.258
- 1.5.257
- 1.5.256
- 1.5.255
- 1.5.254
- 1.5.253
- 1.5.252
- 1.5.251
- 1.5.250
- 1.5.249
- 1.5.248
- 1.5.247
- 1.5.246
- 1.5.245
- 1.5.244
- 1.5.243
- 1.5.242
- 1.5.241
- 1.5.240
- 1.5.239
- 1.5.238
- 1.5.237
- 1.5.236
- 1.5.235
- 1.5.234
- 1.5.233
- 1.5.232
- 1.5.231
- 1.5.230
- 1.5.229
- 1.5.228
- 1.5.227
- 1.5.226
- 1.5.225
- 1.5.224
- 1.5.223
- 1.5.222
- 1.5.221
- 1.5.220
- 1.5.219
- 1.5.218
- 1.5.217
- 1.5.216
- 1.5.215
- 1.5.214
- 1.5.213
- 1.5.212
- 1.5.211
- 1.5.210
- 1.5.209
- 1.5.208
- 1.5.207
- 1.5.206
- 1.5.205
- 1.5.204
- 1.5.203
- 1.5.202
- 1.5.201
- 1.5.200
- 1.5.199
- 1.5.198
- 1.5.197
- 1.5.196
- 1.5.195
- 1.5.194
- 1.5.193
- 1.5.192
- 1.5.191
- 1.5.190
- 1.5.189
- 1.5.188
- 1.5.187
- 1.5.186
- 1.5.185
- 1.5.184
- 1.5.183
- 1.5.182
- 1.5.181
- 1.5.180
- 1.5.179
- 1.5.178
- 1.5.177
- 1.5.176
- 1.5.175
- 1.5.174
- 1.5.173
- 1.5.172
- 1.5.171
- 1.5.170
- 1.5.169
- 1.5.168
- 1.5.167
- 1.5.166
- 1.5.165
- 1.5.164
- 1.5.163
- 1.5.162
- 1.5.161
- 1.5.160
- 1.5.159
- 1.5.158
- 1.5.157
- 1.5.156
- 1.5.155
- 1.5.154
- 1.5.153
- 1.5.152
- 1.5.151
- 1.5.150
- 1.5.149
- 1.5.148
- 1.5.147
- 1.5.146
- 1.5.145
- 1.5.144
- 1.5.143
- 1.5.142
- 1.5.141
- 1.5.140
- 1.5.139
- 1.5.138
- 1.5.137
- 1.5.136
- 1.5.135
- 1.5.134
- 1.5.133
- 1.5.132
- 1.5.131
- 1.5.130
- 1.5.129
- 1.5.128
- 1.5.127
- 1.5.126
- 1.5.125
- 1.5.124
- 1.5.123
- 1.5.122
- 1.5.121
- 1.5.120
- 1.5.119
- 1.5.118
- 1.5.117
- 1.5.116
- 1.5.115
- 1.5.114
- 1.5.113
- 1.5.112
- 1.5.111
- 1.5.110
- 1.5.109
- 1.5.108
- 1.5.107
- 1.5.106
- 1.5.105
- 1.5.104
- 1.5.103
- 1.5.102
- 1.5.101
- 1.5.100
- 1.5.99
- 1.5.98
- 1.5.97
- 1.5.96
- 1.5.95
- 1.5.94
- 1.5.93
- 1.5.92
- 1.5.91
- 1.5.90
- 1.5.89
- 1.5.88
- 1.5.87
- 1.5.86
- 1.5.85
- 1.5.84
- 1.5.83
- 1.5.82
- 1.5.81
- 1.5.80
- 1.5.79
- 1.5.78
- 1.5.77
- 1.5.76
- 1.5.75
- 1.5.74
- 1.5.73
- 1.5.72
- 1.5.71
- 1.5.70
- 1.5.69
- 1.5.68
- 1.5.67
- 1.5.66
- 1.5.65
- 1.5.64
- 1.5.63
- 1.5.62
- 1.5.61
- 1.5.60
- 1.5.59
- 1.5.58
- 1.5.57
- 1.5.56
- 1.5.55
- 1.5.54
- 1.5.53
- 1.5.52
- 1.5.51
- 1.5.50
- 1.5.49
- 1.5.48
- 1.5.47
- 1.5.46
- 1.5.45
- 1.5.44
- 1.5.43
- 1.5.42
- 1.5.41
- 1.5.40
- 1.5.39
- 1.5.38
- 1.5.37
- 1.5.36
- 1.5.35
- 1.5.34
- 1.5.33
- 1.5.32
- 1.5.31
- 1.5.30
- 1.5.29
- 1.5.28
- 1.5.27
- 1.5.26
- 1.5.25
- 1.5.24
- 1.5.23
- 1.5.22
- 1.5.21
- 1.5.20
- 1.5.19
- 1.5.18
- 1.5.17
- 1.5.16
- 1.5.15
- 1.5.14
- 1.5.13
- 1.5.12
- 1.5.11
- 1.5.10
- 1.5.9
- 1.5.8
- 1.5.7
- 1.5.6
- 1.5.5
- 1.5.4
- 1.5.3
- 1.5.2
- 1.5.1
- 1.5.0
- 1.4.103
- 1.4.102
- 1.4.101
- 1.4.100
- 1.4.99
- 1.4.98
- 1.4.97
- 1.4.96
- 1.4.95
- 1.4.94
- 1.4.93
- 1.4.92
- 1.4.91
- 1.4.90
- 1.4.89
- 1.4.88
- 1.4.87
- 1.4.86
- 1.4.85
- 1.4.84
- 1.4.83
- 1.4.82
- 1.4.81
- 1.4.80
- 1.4.79
- 1.4.78
- 1.4.77
- 1.4.76
- 1.4.75
- 1.4.74
- 1.4.73
- 1.4.72
- 1.4.71
- 1.4.70
- 1.4.69
- 1.4.68
- 1.4.67
- 1.4.66
- 1.4.65
- 1.4.64
- 1.4.63
- 1.4.62
- 1.4.61
- 1.4.60
- 1.4.59
- 1.4.58
- 1.4.57
- 1.4.56
- 1.4.55
- 1.4.54
- 1.4.53
- 1.4.52
- 1.4.51
- 1.4.50
- 1.4.49
- 1.4.48
- 1.4.44
- 1.4.43
- 1.4.42
- 1.4.41
- 1.4.40
- 1.4.39
- 1.4.38
- 1.4.37
- 1.4.36
- 1.4.35
- 1.4.34
- 1.4.33
- 1.4.32
- 1.4.31
- 1.4.30
- 1.4.29
- 1.4.28
- 1.4.27
- 1.4.26
- 1.4.25
- 1.4.24
- 1.4.23
- 1.4.22
- 1.4.21
- 1.4.20
- 1.4.19
- 1.4.18
- 1.4.17
- 1.4.16
- 1.4.15
- 1.4.14
- 1.4.13
- 1.4.12
- 1.4.11
- 1.4.10
- 1.4.9
- 1.4.8
- 1.4.7
- 1.4.6
- 1.4.5
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.14
- 1.3.13
- 1.3.12
- 1.3.11
- 1.3.10
- 1.3.9
- 1.3.8
- 1.3.7
- 1.3.6
- 1.3.5
- 1.3.4
- 1.2.56
- 1.2.55
- 1.2.54
- 1.2.53
- 1.2.52
- 1.2.51
- 1.2.50
- 1.2.49
- 1.2.48
- 1.2.47
- 1.2.46
- 1.2.45
- 1.2.44
- 1.2.43
- 1.2.42
- 1.2.41
- 1.2.40
- 1.2.39
- 1.2.38
- 1.2.37
- 1.2.36
- 1.2.34
- 1.2.33
- 1.2.32
- 1.2.31
- 1.2.30
- 1.2.29
- 1.2.28
- 1.2.27
- 1.2.26
- 1.2.25
- 1.2.24
- 1.2.23
- 1.2.22
- 1.2.21
- 1.2.17
- 1.2.16
- 1.2.15
- 1.2.14
- 1.2.13
- 1.2.12
- 1.2.11
- 1.2.10
- 1.2.9
- 1.2.8
- 1.2.7
- 1.2.6
- v1.2.5
- v1.2.4
This package is auto-updated.
Last update: 2026-10-04 18:03:18 UTC
README
A Symfony bundle that renders a server-driven DataTables 3 table from an API Platform collection endpoint, using Twig Components and Stimulus.
API Grid now requires survos/grid-bundle and extends its GridController. Grid owns DataTables core (currently 3.1.3), the Bootstrap 5 / Tabler styling, extension pins, initialization and teardown. API Grid owns the API Platform requests, server-side pagination, facets, filters, selection actions and browser-side Twig rendering. Both render HTML tables; choose base Grid for in-memory rows or a single JSON fetch.
Key features:
- Zero-config column inference from PHP attributes (
#[Field],#[ApiFilter]) - ColumnControl — per-column sort / search / facet dropdowns (default)
- SearchBuilder — modal query builder with AND/OR logic
- Responsive, multi-row select, bulk actions, offcanvas detail panel
- Custom cell rendering via inline Twig blocks (server-side Twig, zero JS templating required)
- Bootstrap 5 / Tabler-compatible out of the box
Requirements
- PHP 8.5+
- Symfony 8.1+ for the shared Grid dependency
- API Platform 4.1+ or 5.x
survos/grid-bundleandsurvos/field-bundle(installed as dependencies)
Install
composer req survos/api-grid-bundle
Assets are registered via Symfony UX and AssetMapper. The canonical package names are @survos/api-grid-bundle and @survos/grid-bundle; remove obsolete application overrides using @survos/api-grid or @survos/grid. Do not run importmap:require @survos/api-grid.
{
"controllers": {
"@survos/api-grid-bundle": {
"api-grid": {"enabled": true, "fetch": "lazy", "autoimport": {}}
},
"@survos/grid-bundle": {
"grid": {"enabled": true, "fetch": "lazy", "autoimport": {}}
}
}
}
Run php bin/console importmap:install after upgrading and clear the application cache. DataTables pins come from Grid's symfony.importmap manifest. Reconcile stale application pins with that manifest; changing npm dependencies alone does not update Flex importmaps.
Core CSS is imported by the controller. Extensions and their CSS load only when the selected grid features require them. Remove old global DataTables CSS autoimports from application controller configuration. Detail dialogs reuse window.bootstrap when supplied by the host or import Modal / Offcanvas from the host's @tabler/core; Tabler does not create that global itself.
The unqualified <twig:item_grid> component now belongs to Grid. Use <twig:api_item_grid> for API Grid's legacy recursive detail component. API Grid retains its richer Column metadata and API-specific components.
Verification status
The shared base is exercised by Showcase's /browse page over 23 Site entities. Search, sorting, pagination, page-size changes and navigation away/back passed locally on October 4, 2026. That page uses <twig:grid> and does not prove API-backed behavior. Tree-demo's /playground/topics provides Grid and Grid + Responsive comparisons; its existing /topic/index is the API Grid follow-up target.
The shared PHP suite passes 10 tests / 116 assertions, and the JavaScript suite passes six tests covering lazy assets, disconnect/reconnect and API controller inheritance. Live API pagination, facets and detail dialogs still need application smoke tests after publication. See Grid's docs/testing.md and docs/upgrading.md in the mono repository for the full checks and migration notes.
Quick Start
This four-step setup gives you a working sortable, searchable, filterable grid.
1. Annotate the entity
Add API Platform filters to the entity. The bundle reads these annotations to infer which columns are sortable, searchable, and filterable without any explicit column configuration.
// src/Entity/Video.php use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use Survos\ApiGridBundle\Api\Filter\MultiFieldSearchFilter; use Survos\ApiGridBundle\Api\Filter\FacetsFieldSearchFilter; #[ApiResource] #[GetCollection(name: self::COLLECTION_ROUTE)] #[ApiFilter(OrderFilter::class, properties: ['title', 'year'])] #[ApiFilter(MultiFieldSearchFilter::class, properties: ['title', 'description'])] #[ApiFilter(FacetsFieldSearchFilter::class, properties: ['genre', 'status'])] class Video { public const COLLECTION_ROUTE = 'api-video'; // ... }
If you use survos/field-bundle, annotate properties instead — see Field-Bundle Integration.
2. Compute the collection URL in the controller
Resolve the URL server-side so the template gets a plain string. This keeps the Twig template simple and makes debugging straightforward (the URL is visible in the browser network tab).
// src/Controller/VideoController.php use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\IriConverterInterface; use App\Entity\Video; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; class VideoController extends AbstractController { #[Route('/videos', name: 'app_video_browse')] public function browse(IriConverterInterface $iriConverter): Response { return $this->render('video/browse.html.twig', [ 'class' => Video::class, 'apiGetCollectionUrl' => $iriConverter->getIriFromResource( Video::class, operation: new GetCollection(name: Video::COLLECTION_ROUTE) ), ]); } }
3. Render the grid in Twig
{# templates/video/browse.html.twig #} {% set columns = [ col('id'), col('year', sortable: true), col('title', searchable: true, sortable: true), col('genre', browsable: true), col('status', browsable: true), ] %} <twig:api_grid :class="class" :apiGetCollectionUrl="apiGetCollectionUrl" :columns="columns" :caller="_self" />
The col() Twig function is provided by the bundle. Each call returns a Column object. Arguments map directly to Column properties — see Column Reference.
4. Enable client-side pagination
DataTables uses limit/offset (not page). The bundle ships a SlicePaginationExtension that handles this, but API Platform must allow client control:
# config/packages/api_platform.yaml api_platform: collection: pagination: client_items_per_page: true client_enabled: true
Entity Setup
Sorting
#[ApiFilter(OrderFilter::class, properties: ['title', 'year', 'createdAt'])]
Global search
The Stimulus controller sends the search box value as individual field parameters (one per searchable column). For a single unified ?search= parameter, use MultiFieldSearchFilter:
#[ApiFilter(MultiFieldSearchFilter::class, properties: ['title', 'description', 'tags'])]
Facet filters (ColumnControl / SearchBuilder)
#[ApiFilter(FacetsFieldSearchFilter::class, properties: ['genre', 'status', 'country'])]
Columns marked browsable: true use this filter. In ColumnControl mode they render as searchable dropdowns; in SearchBuilder mode they appear as criteria fields.
Field-Bundle Integration
If survos/field-bundle is installed, add #[Field] to entity properties instead of #[ApiFilter]. The bundle reads FieldDescriptor objects from FieldReader and uses them as the authoritative source for column settings.
use Survos\FieldBundle\Attribute\Field; use Survos\FieldBundle\Enum\Widget; class Video { #[Field(searchable: true, sortable: true)] public string $title; #[Field(sortable: true)] public int $year; #[Field(filterable: true, widget: Widget::Select)] public string $genre; #[Field(filterable: true, widget: Widget::Boolean)] public bool $published; }
#[Field] drives: searchable, sortable, browsable (via filterable + widget), visible, width, widget.
Explicit col() arguments always override #[Field] defaults. See Field-Bundle Integration for the full layering rules.
Twig Component Reference
<twig:api_grid :class="class" {# FQCN of the entity #} :apiGetCollectionUrl="url" {# Collection endpoint URL #} :columns="columns" {# array of col() objects or plain arrays #} :caller="_self" {# enables inline <twig:block> rendering #} columnControl=true {# enable ColumnControl (default: false) #} searchBuilder=true {# enable SearchBuilder (default: false) #} :pageLength="50" {# rows per page (default: 50) #} :defaultOrder="'year:desc'" {# initial sort; "field:dir" or "a:asc,b:desc" #} :showRoute="'app_video_show'" {# route name → opens detail in offcanvas panel #} select=true {# prepend checkbox column for multi-select #} :bulkActions="bulkActions" {# array of bulk-action definitions #} :filter="filter" {# initial filter values, merged into API params #} :buttons="buttons" {# extra toolbar buttons #} :scrollY="'70vh'" {# table body height (CSS value) #} :tableId="'my-table'" {# HTML id for the <table> element #} />
Column Reference
col(
'fieldName', {# positional: property name in the serialized API response #}
title: 'Label', {# header label (defaults to property name) #}
sortable: true, {# enable column ordering #}
searchable: true, {# include in global search #}
browsable: true, {# expose as a facet/filter in ColumnControl or SearchBuilder #}
visible: true, {# show column by default (false = hidden, toggleable) #}
width: '10rem', {# CSS width hint #}
widget: 'range', {# widget hint: text | select | range | date | boolean #}
route: 'app_video_show', {# wrap cell value in <a href="..."> using js-twig browser routing #}
responsivePriority: 1, {# DataTables responsive priority (lower = higher priority) #}
titleAttr: 'Tooltip text', {# HTML title attribute on the <th> #}
order: 10, {# display order within the column list #}
condition: true, {# false removes the column entirely (useful with variables) #}
)
Columns can also be passed as plain arrays (backward compatible):
{% set columns = [
'id',
{name: 'title', sortable: true, searchable: true},
{name: 'status', browsable: true},
] %}
Widget Modes
The grid supports two filter UI modes. See docs/widgets.md for full documentation.
ColumnControl (recommended)
Per-column dropdowns embedded directly in the column headers. Ideal when the page already has a sidebar (e.g. EasyAdmin, Tabler).
<twig:api_grid ... columnControl=true />
Browsable columns (browsable: true) render as searchable dropdown lists.
Columns with widget: 'range' render as min/max number inputs.
SearchBuilder
A modal query builder with AND/OR logic. Browsable columns appear as criteria fields.
<twig:api_grid ... searchBuilder=true />
Custom Cell Rendering
Pass :caller="_self" and add <twig:block name="fieldName"> blocks to override rendering for specific columns. The variable row contains the full deserialized API response row.
{% set columns = [
col('youtubeId'),
col('title', sortable: true),
col('year', sortable: true),
] %}
<twig:api_grid
:class="class"
:apiGetCollectionUrl="apiGetCollectionUrl"
:columns="columns"
:caller="_self"
>
<twig:block name="youtubeId">
<a target="_blank" href="https://youtube.com/watch?v={{ row.youtubeId }}">
<img src="{{ row.thumbnailUrl }}" height="60" alt="{{ row.title }}"/>
</a>
</twig:block>
</twig:api_grid>
Blocks are compiled server-side by the js-twig-bundle Twig-to-JS bridge and executed in the browser by the Stimulus controller, so you can use Symfony Twig functions (path(), asset(), trans(), etc.) inside the blocks.
Row Actions
Add a non-searchable _actions column and render it with a <twig:block>:
{% set columns = [
col('title'),
col('status', browsable: true),
col(name: '_actions', title: '', sortable: false, searchable: false),
] %}
<twig:api_grid :class="class" :apiGetCollectionUrl="apiGetCollectionUrl"
:columns="columns" :caller="_self">
<twig:block name="_actions">
<a class="btn btn-sm btn-outline-secondary"
href="{{ path('app_video_show', row.rp) }}">View</a>
<a class="btn btn-sm btn-outline-primary"
href="{{ path('app_video_edit', row.rp) }}">Edit</a>
</twig:block>
</twig:api_grid>
row.rp contains the route parameters exposed in the serialized API response. Include the rp property in the entity's serialization group.
Stimulus helpers work too:
<twig:block name="_actions"> <button {{ stimulus_action('modal-form', 'open', 'click', { url: path('app_video_edit', row.rp) }) }} class="btn btn-sm btn-outline-primary">Edit</button> </twig:block>
Offcanvas Detail Panel
Pass showRoute to add a "View" button per row. Clicking it fetches the route and renders the HTML in a Bootstrap Offcanvas panel — no page navigation.
<twig:api_grid ... :showRoute="'app_video_show'" />
The route receives row.rp as parameters. Add ?_page_content_only=1 handling in the controller/template to return only the content fragment.
Bulk Actions
Enable select=true and define bulkActions to let users select rows and POST their IDs to a server endpoint.
// in your controller $bulkActions = [ [ 'id' => 'publish', 'label' => 'Publish selected', 'url' => $this->generateUrl('app_video_bulk_publish'), 'destructive' => false, 'confirm' => true, 'confirmMessage' => 'Publish {count} video(s)?', ], [ 'id' => 'delete', 'label' => 'Delete selected', 'url' => $this->generateUrl('app_video_bulk_delete'), 'destructive' => true, 'confirm' => true, ], ];
<twig:api_grid :class="class" :apiGetCollectionUrl="apiGetCollectionUrl" :columns="columns" select=true :bulkActions="bulkActions" :entityClass="class" />
The controller receives ids[] (array of entity IDs) and className (FQCN) as a POST form submission. A CSRF token is included automatically.
Admin Browser
A generic admin browser route is registered at /admin/browse/{code}. The code is derived from the bundle/app prefix plus the entity short name:
Survos\OutreachBundle\Entity\Contact→/admin/browse/outreach_contactApp\Entity\Video→/admin/browse/app_video
/admin/browse lists all registered Doctrine entities.
If a route named {code}_show exists, the browser passes it as showRoute, enabling the offcanvas detail panel automatically.
Backend Filters
Repository facet counts
Facet counts (shown in ColumnControl dropdowns) require a repository method that can count distinct values per field. Install the trait from survos/core-bundle:
use Survos\CoreBundle\Traits\QueryBuilderHelperInterface; use Survos\CoreBundle\Traits\QueryBuilderHelperTrait; class VideoRepository extends ServiceEntityRepository implements QueryBuilderHelperInterface { use QueryBuilderHelperTrait; }
Pagination
The bundle's SlicePaginationExtension translates limit/offset from the DataTables request into Doctrine range queries. This runs automatically when the bundle is installed.
Further Reading
- docs/widgets.md — ColumnControl and SearchBuilder in depth
- docs/field-bundle.md — Attribute-driven configuration with
#[Field] - docs/implementation.md — Architecture: Twig component, Stimulus controller, normalizer, paginator