$npx -y skills add edutrul/drupal-ai --skill drupal-stateDrupal State API — storing and retrieving runtime state that persists across requests but is not configuration.
| 1 | # Drupal State API |
| 2 | |
| 3 | State is for runtime data that should persist across requests but is NOT configuration. Use Config for user-managed settings. Use State for internal operational data (last cron run, migration status, etc.). |
| 4 | |
| 5 | ## Basic Usage |
| 6 | |
| 7 | ```php |
| 8 | use Drupal\Core\State\StateInterface; |
| 9 | |
| 10 | // Inject |
| 11 | public function __construct( |
| 12 | private readonly StateInterface $state, |
| 13 | ) {} |
| 14 | |
| 15 | // Get |
| 16 | $value = $this->state->get('my_module.last_run'); |
| 17 | $value = $this->state->get('my_module.last_run', $default_value); |
| 18 | |
| 19 | // Set |
| 20 | $this->state->set('my_module.last_run', \Drupal::time()->getRequestTime()); |
| 21 | |
| 22 | // Delete |
| 23 | $this->state->delete('my_module.last_run'); |
| 24 | |
| 25 | // Get multiple |
| 26 | $values = $this->state->getMultiple(['key1', 'key2']); |
| 27 | |
| 28 | // Set multiple |
| 29 | $this->state->setMultiple([ |
| 30 | 'my_module.status' => 'active', |
| 31 | 'my_module.count' => 42, |
| 32 | ]); |
| 33 | |
| 34 | // Delete multiple |
| 35 | $this->state->deleteMultiple(['key1', 'key2']); |
| 36 | ``` |
| 37 | |
| 38 | ## Naming Convention |
| 39 | |
| 40 | Use a namespaced key: `module_name.key_name` |
| 41 | |
| 42 | ```php |
| 43 | // Good |
| 44 | $this->state->get('my_module.last_import_time'); |
| 45 | $this->state->get('my_module.processed_count'); |
| 46 | |
| 47 | // Bad |
| 48 | $this->state->get('last_import'); // Not namespaced |
| 49 | ``` |
| 50 | |
| 51 | ## State vs Config vs Cache |
| 52 | |
| 53 | | Storage | Use for | Exportable | |
| 54 | |---|---|---| |
| 55 | | **State** | Runtime operational data (last run times, counters, flags) | No | |
| 56 | | **Config** | User-managed settings, site configuration | Yes (via cex/cim) | |
| 57 | | **Cache** | Expensive computed data, invalidated on change | No (temporary) | |
| 58 | | **UserData** | Per-user preferences/data | No | |
| 59 | |
| 60 | ## Common Use Cases |
| 61 | |
| 62 | ```php |
| 63 | // Track last cron run |
| 64 | $this->state->set('my_module.cron_last', \Drupal::time()->getRequestTime()); |
| 65 | |
| 66 | // Feature flag (internal) |
| 67 | $isEnabled = $this->state->get('my_module.feature_enabled', FALSE); |
| 68 | |
| 69 | // Track migration progress |
| 70 | $this->state->set('my_module.migration_offset', $offset); |
| 71 | |
| 72 | // Store external API token (not sensitive — use KeyModule for secrets) |
| 73 | $this->state->set('my_module.api_token_expires', $expires); |
| 74 | ``` |
| 75 | |
| 76 | ## Drush State Commands |
| 77 | |
| 78 | ```bash |
| 79 | # Get |
| 80 | drush state:get my_module.last_run |
| 81 | |
| 82 | # Set |
| 83 | drush state:set my_module.feature_enabled 1 |
| 84 | |
| 85 | # Delete |
| 86 | drush state:del my_module.last_run |
| 87 | ``` |
| 88 | |
| 89 | ## Service ID |
| 90 | |
| 91 | `state` |