|
| 1 | +# Global Classes — Extension Points |
| 2 | + |
| 3 | +Elementor Core Intermediate |
| 4 | + |
| 5 | +This page documents the integration surface 3rd-party code can use to read, write, observe, and extend Global Classes in Elementor 4.1+. For background, see [Architecture Overview](./01-overview.md). For breaking changes from prior versions, see [Migration Guide](./02-migration-guide.md). |
| 6 | + |
| 7 | +> **Note.** Anything not listed here — internal classes, private methods, post meta written by `Global_Classes_Relations`, the `e_global_class` post type internals — is implementation detail and may change between releases. Read meta keys at your own risk; do not write to them. |
| 8 | +
|
| 9 | +## # REST endpoints |
| 10 | + |
| 11 | +All endpoints live under the `elementor/v1` namespace. All read endpoints accept an optional `context` argument: `frontend` (default) or `preview`. |
| 12 | + |
| 13 | +### # `GET /elementor/v1/global-classes` |
| 14 | + |
| 15 | +Lightweight index of all global classes for the active kit, in display order. |
| 16 | + |
| 17 | +| Argument | Type | Required | Default | Notes | |
| 18 | +| -------- | ---- | -------- | ------- | ----- | |
| 19 | +| `context` | string | no | `frontend` | One of `frontend`, `preview`. | |
| 20 | + |
| 21 | +**Permission:** `is_user_logged_in()`. |
| 22 | + |
| 23 | +**Response:** |
| 24 | + |
| 25 | +```json |
| 26 | +{ |
| 27 | + "data": [ |
| 28 | + { "id": "g-abc", "label": "Card" }, |
| 29 | + { "id": "g-def", "label": "Button" } |
| 30 | + ] |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +This endpoint returns labels only. Use `/global-classes/styles` or `/global-classes/post` for full class data. |
| 35 | + |
| 36 | +### # `GET /elementor/v1/global-classes/post` |
| 37 | + |
| 38 | +Full class definitions for the classes referenced by a specific document, plus their order. |
| 39 | + |
| 40 | +| Argument | Type | Required | Default | Notes | |
| 41 | +| -------- | ---- | -------- | ------- | ----- | |
| 42 | +| `post_id` | integer | yes | — | The Elementor document ID. | |
| 43 | +| `context` | string | no | `frontend` | One of `frontend`, `preview`. | |
| 44 | + |
| 45 | +**Permission:** `is_user_logged_in()`. |
| 46 | + |
| 47 | +**Response:** |
| 48 | + |
| 49 | +```json |
| 50 | +{ |
| 51 | + "data": { |
| 52 | + "g-abc": { "id": "g-abc", "label": "Card", "type": "class", "variants": [ … ] } |
| 53 | + }, |
| 54 | + "meta": { "order": [ "g-abc" ] } |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +`meta.order` is the intersection of the global class order with the IDs used by the document. |
| 59 | + |
| 60 | +### # `GET /elementor/v1/global-classes/styles` |
| 61 | + |
| 62 | +Bulk fetch of class definitions by ID list. Used by the editor for lazy loading classes not in the initial document index. |
| 63 | + |
| 64 | +| Argument | Type | Required | Default | Notes | |
| 65 | +| -------- | ---- | -------- | ------- | ----- | |
| 66 | +| `ids` | string | yes | — | Comma-separated list of class IDs. | |
| 67 | +| `context` | string | no | `frontend` | One of `frontend`, `preview`. | |
| 68 | + |
| 69 | +**Permission:** `is_user_logged_in()`. |
| 70 | + |
| 71 | +**Response:** Same shape as `/global-classes/post`. IDs that don't exist resolve to `null` in `data` and are omitted from `meta.order`. |
| 72 | + |
| 73 | +```json |
| 74 | +{ |
| 75 | + "data": { |
| 76 | + "g-abc": { "id": "g-abc", "label": "Card", "type": "class", "variants": [ … ] }, |
| 77 | + "g-missing": null |
| 78 | + }, |
| 79 | + "meta": { "order": [ "g-abc" ] } |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +> **Security note.** `is_user_logged_in()` allows any authenticated user to read class IDs and definitions. If your site needs stricter visibility, layer your own capability check via a `rest_pre_dispatch` filter scoped to these routes. |
| 84 | +
|
| 85 | +### # `PUT /elementor/v1/global-classes` |
| 86 | + |
| 87 | +Create, update, delete, and reorder classes. |
| 88 | + |
| 89 | +| Argument | Type | Required | Default | Notes | |
| 90 | +| -------- | ---- | -------- | ------- | ----- | |
| 91 | +| `context` | string | no | `frontend` | One of `frontend`, `preview`. | |
| 92 | +| `changes` | object | yes | — | `{ added: string[], deleted: string[], modified: string[], order?: boolean }`. `additionalProperties: false`. | |
| 93 | +| `items` | object | yes | — | Map of `class_id → { id, label, type, variants }` for **touched** items only (added and modified). | |
| 94 | +| `order` | string[] | yes | — | The full ordered list of class IDs after the operation. | |
| 95 | + |
| 96 | +**Permission:** `current_user_can( Add_Capabilities::UPDATE_CLASS )`. |
| 97 | + |
| 98 | +**Responses:** |
| 99 | + |
| 100 | +- `204 No Content` on success. |
| 101 | +- `200 OK` with `{ "code": "DUPLICATED_LABEL", "modifiedLabels": { … } }` when the server auto-renamed duplicate labels. |
| 102 | +- `400 Bad Request` with `code: "invalid_items" | "invalid_order" | "global_classes_limit_exceeded"`. The limit error carries `meta.current_count` and `meta.max_allowed`. |
| 103 | + |
| 104 | +The server enforces a hard limit of **1000 classes** (`Global_Classes_REST_API::MAX_ITEMS`). |
| 105 | + |
| 106 | +Set `changes.order = true` when the order changes, otherwise document-wide style caches for the context will not be invalidated. |
| 107 | + |
| 108 | +### # `GET /elementor/v1/global-classes/usage` |
| 109 | + |
| 110 | +Detailed usage report of applied global classes across the site. |
| 111 | + |
| 112 | +| Argument | Type | Required | Default | Notes | |
| 113 | +| -------- | ---- | -------- | ------- | ----- | |
| 114 | +| `context` | string | no | `frontend` | One of `frontend`, `preview`. | |
| 115 | + |
| 116 | +**Permission:** `current_user_can( 'manage_options' )`. |
| 117 | + |
| 118 | +## # PHP actions |
| 119 | + |
| 120 | +### # `elementor/global_classes/update` |
| 121 | + |
| 122 | +Fired after every mutation of the global classes set (via `apply_changes()` or `put()`). |
| 123 | + |
| 124 | +**Signature:** |
| 125 | + |
| 126 | +```php |
| 127 | +do_action( |
| 128 | + 'elementor/global_classes/update', |
| 129 | + string $context, // 'frontend' | 'preview' |
| 130 | + array $changes // [ 'added' => string[], 'deleted' => string[], 'modified' => string[], 'order' => bool ] |
| 131 | +); |
| 132 | +``` |
| 133 | + |
| 134 | +**Use it to:** invalidate your own caches, mirror class data to an external system, track usage analytics. Listeners must register with `accepted_args = 2`. |
| 135 | + |
| 136 | +```php |
| 137 | +add_action( 'elementor/global_classes/update', function( $context, $changes ) { |
| 138 | + if ( 'frontend' !== $context ) { |
| 139 | + return; |
| 140 | + } |
| 141 | + |
| 142 | + foreach ( $changes['deleted'] as $class_id ) { |
| 143 | + my_plugin_drop_cached_css( $class_id ); |
| 144 | + } |
| 145 | +}, 10, 2 ); |
| 146 | +``` |
| 147 | + |
| 148 | +### # `elementor/atomic-widgets/styles/register` |
| 149 | + |
| 150 | +Fired during style registration. Lets you register custom style sources into `Atomic_Styles_Manager` alongside the built-in global styles. |
| 151 | + |
| 152 | +**Signature:** |
| 153 | + |
| 154 | +```php |
| 155 | +do_action( |
| 156 | + 'elementor/atomic-widgets/styles/register', |
| 157 | + \Elementor\Modules\AtomicWidgets\Styles\Atomic_Styles_Manager $manager, |
| 158 | + array $post_ids |
| 159 | +); |
| 160 | +``` |
| 161 | + |
| 162 | +Listeners must register with `accepted_args = 2`. Use the `[ <your_key>, $post_id, $context ]` tuple convention when registering per-document styles. |
| 163 | + |
| 164 | +### # `elementor/atomic-widgets/styles/clear` |
| 165 | + |
| 166 | +Fired when style caches should be invalidated. The `$key` argument is a hierarchical tuple — listeners may receive any prefix of `[ <key>, $post_id, $context ]`. |
| 167 | + |
| 168 | +**Signature:** |
| 169 | + |
| 170 | +```php |
| 171 | +do_action( 'elementor/atomic-widgets/styles/clear', array $key ); |
| 172 | +``` |
| 173 | + |
| 174 | +For Global Classes specifically, the key forms are: |
| 175 | + |
| 176 | +| Key | Meaning | |
| 177 | +| --- | ------- | |
| 178 | +| `[ 'global' ]` | Full clear, all documents and contexts. | |
| 179 | +| `[ 'global', $context ]` | Clear one context, all documents. | |
| 180 | +| `[ 'global', $post_id ]` | Clear one document, both contexts. | |
| 181 | +| `[ 'global', $post_id, $context ]` | Clear one document and context. | |
| 182 | + |
| 183 | +If your plugin caches anything keyed on the old `[ 'global', $context ]` form, also handle the per-document forms. |
| 184 | + |
| 185 | +## # PHP filters |
| 186 | + |
| 187 | +### # `elementor/atomic-widgets/settings/transformers/classes` |
| 188 | + |
| 189 | +Unchanged contract. Internally now resolves labels via `Global_Classes_Repository::all_labels()` instead of `all()->get_items()` (faster, transparent to consumers). |
| 190 | + |
| 191 | +### # `elementor/kit/meta_to_preserve_on_kit_import` |
| 192 | + |
| 193 | +Used to preserve kit-level metadata across kit imports. Global Classes registers four keys here (order, frontend labels, preview labels, sync map). 3rd-party modules that store kit-level metadata they want preserved across kit imports can add their own keys: |
| 194 | + |
| 195 | +```php |
| 196 | +add_filter( 'elementor/kit/meta_to_preserve_on_kit_import', function( array $meta_keys ) { |
| 197 | + $meta_keys[] = 'my_plugin_kit_meta_key'; |
| 198 | + return $meta_keys; |
| 199 | +} ); |
| 200 | +``` |
| 201 | + |
| 202 | +### # `elementor/template_library/export/build_snapshots`, `…/extract_snapshots`, `…/import/process_content` |
| 203 | + |
| 204 | +Existing template-library filters. Global Classes hooks into all three to participate in template export/import. No signature changes here — the contracts are documented elsewhere. Mentioned here only because Global Classes is now one of their consumers. |
| 205 | + |
| 206 | +## # PHP API: `Global_Classes_Repository` |
| 207 | + |
| 208 | +```php |
| 209 | +use Elementor\Modules\GlobalClasses\Global_Classes_Repository; |
| 210 | + |
| 211 | +$repository = Global_Classes_Repository::make(); |
| 212 | +// or, scoped to a specific kit: |
| 213 | +$repository = Global_Classes_Repository::make( $kit ); |
| 214 | +``` |
| 215 | + |
| 216 | +### # Context |
| 217 | + |
| 218 | +```php |
| 219 | +$repository->set_preview( true ); // operate on preview/draft state |
| 220 | +$repository->set_preview( false ); // operate on published state (default) |
| 221 | +$repository->set_kit( $kit ); // bind to a non-active kit |
| 222 | +``` |
| 223 | + |
| 224 | +### # Reads |
| 225 | + |
| 226 | +| Method | Returns | Notes | |
| 227 | +| ------ | ------- | ----- | |
| 228 | +| `all_labels()` | `array<string, string>` | `class_id → label`, in display order. Cheap. Prefer over `all()`. | |
| 229 | +| `get( string $class_id )` | `?array` | Full item or `null`. | |
| 230 | +| `get_by_ids( array $class_ids )` | `array<string, array>` | `id → item` map. Missing IDs are absent. | |
| 231 | +| `get_order()` | `string[]` | The current ordered ID list. | |
| 232 | +| `each_item( callable $cb, bool $skip_migration = false, int $batch_size = 100 )` | `void` | Streams every class through `$cb` in display order. Use for bulk processing. | |
| 233 | +| `all( bool $force = false )` | `Global_Classes` | **Heavy.** Loads every class into memory. Avoid on hot paths. | |
| 234 | + |
| 235 | +### # Writes |
| 236 | + |
| 237 | +| Method | Notes | |
| 238 | +| ------ | ----- | |
| 239 | +| `apply_changes( array $touched_items, array $changes, array $order ) : void` | Used by the REST `PUT` path. `$changes` shape matches `elementor/global_classes/update`. Persists only touched items and the order/labels diff. | |
| 240 | +| `put( array $items, array $order )` | "Replace everything" semantics. Diffs internally against the CPT, persists, and fires `elementor/global_classes/update` with the computed `$changes`. | |
| 241 | +| `update_order_and_labels( array $order, array $new_labels ) : void` | Updates only the kit-level order and label map. Does not touch class data. | |
| 242 | +| `delete_all() : void` | Removes every class for the current context. | |
| 243 | + |
| 244 | +Both `apply_changes()` and `put()` fire `elementor/global_classes/update` at the end. |
| 245 | + |
| 246 | +## # Post type and post meta (read-only) |
| 247 | + |
| 248 | +These exist as implementation detail. You may read them, but do not write to them — go through `Global_Classes_Repository` or `Global_Classes_Relations` instead. Treat the exact key names as an implementation detail that may change. |
| 249 | + |
| 250 | +### # The `e_global_class` post type |
| 251 | + |
| 252 | +```php |
| 253 | +\Elementor\Modules\GlobalClasses\Global_Class_Post_Type::CPT; // 'e_global_class' |
| 254 | +``` |
| 255 | + |
| 256 | +Registered with `public => false`, `supports => [ 'title' ]`. The post `title` is the class label. |
| 257 | + |
| 258 | +### # Meta on `e_global_class` posts |
| 259 | + |
| 260 | +| Constant | Key | Purpose | |
| 261 | +| -------- | --- | ------- | |
| 262 | +| `Global_Class_Post::META_KEY_ID` | `_elementor_global_class_id` | The user-facing class ID (`g-abc`). | |
| 263 | +| `Global_Class_Post::META_KEY_DATA` | `_elementor_global_class_data` | `{ type, variants, … }` for frontend. | |
| 264 | +| `Global_Class_Post::META_KEY_DATA_PREVIEW` | `_elementor_global_class_data_preview` | Same, for preview. | |
| 265 | +| `Global_Classes_Relations::META_KEY_CLASS_RELATED_POSTS_FRONTEND` | `_elementor_global_class_using_documents` | Reverse index: document IDs that use this class (frontend). | |
| 266 | +| `Global_Classes_Relations::META_KEY_CLASS_RELATED_POSTS_PREVIEW` | `_elementor_global_class_using_documents_preview` | Same, for preview. | |
| 267 | + |
| 268 | +### # Meta on document posts |
| 269 | + |
| 270 | +| Constant | Key | Purpose | |
| 271 | +| -------- | --- | ------- | |
| 272 | +| `Global_Classes_Relations::META_KEY_FRONTEND` | `_elementor_used_global_class` | Multi-value: class IDs the document references (frontend). | |
| 273 | +| `Global_Classes_Relations::META_KEY_PREVIEW` | `_elementor_used_global_class_preview` | Same, for preview. | |
| 274 | +| `Global_Classes_Relations::META_KEY_USAGE_INDEXED_FRONTEND` | `_elementor_global_class_usage_indexed` | `'1'` once the document has been indexed. | |
| 275 | +| `Global_Classes_Relations::META_KEY_USAGE_INDEXED_PREVIEW` | `_elementor_global_class_usage_indexed_preview` | Same, for preview. | |
| 276 | + |
| 277 | +## # Kit-level meta keys (read-only) |
| 278 | + |
| 279 | +These live on the active Kit post. Again — read only. |
| 280 | + |
| 281 | +| Constant | Purpose | |
| 282 | +| -------- | ------- | |
| 283 | +| `Global_Classes_Order::META_KEY` | The global class display order. | |
| 284 | +| `Global_Classes_Labels::META_KEY_FRONTEND` | `class_id → label` map (frontend). | |
| 285 | +| `Global_Classes_Labels::META_KEY_PREVIEW` | Same, for preview. | |
| 286 | +| `Global_Classes_Sync_Map::META_KEY` | Design-system sync map. | |
| 287 | +| `Global_Classes_Post_IDs` storage | `class_id → e_global_class post ID` lookup. | |
| 288 | + |
| 289 | +All four are preserved on kit import via the `elementor/kit/meta_to_preserve_on_kit_import` filter — see [above](#kit-import-meta-preservation). |
| 290 | + |
| 291 | +## # Notes and constraints |
| 292 | + |
| 293 | +- The hard ceiling on global classes is `Global_Classes_REST_API::MAX_ITEMS = 1000`. Server returns HTTP 400 / `global_classes_limit_exceeded` if the post-mutation count would exceed it. |
| 294 | +- The DB migration `Migrate_To_Posts` runs once on upgrade (DB version `1 → 2`). It is idempotent (skipped if any `e_global_class` post exists), and it intentionally leaves the legacy kit meta in place — that meta is not the source of truth after migration, and reading it will return stale data. |
| 295 | +- `Global_Classes_Repository::all()` is heavy. Use `all_labels()` + `get_by_ids()` or `each_item()` instead. |
| 296 | +- The reverse index on `e_global_class` posts is maintained by `Global_Classes_Relations`. Code that writes `_elementor_used_global_class` directly will desync it until the document is next saved through the editor. |
0 commit comments