├── .ci
└── update-libraries.php
├── .editorconfig
├── .gitignore
├── .php-cs-fixer.dist.php
├── .wp-org-assets
├── HTMX-logo-1600x1600.jpg
├── banner-1544x500.png
├── banner-772x250.png
├── icon-1024x1024.png
├── icon-128x128.png
├── icon-256x256.png
└── screenshot-1.jpg
├── CHANGELOG.md
├── FAQ.md
├── LICENSE
├── README.md
├── README.txt
├── SECURITY.md
├── api-for-htmx.php
├── assets
├── index.html
└── js
│ ├── index.html
│ └── libs
│ ├── LICENSE-alpinejs.md
│ ├── LICENSE-datastar.md
│ ├── LICENSE-htmx.md
│ ├── LICENSE-hyperscript.md
│ ├── _hyperscript.js
│ ├── _hyperscript.min.js
│ ├── alpine-ajax.min.js
│ ├── alpinejs.js
│ ├── alpinejs.min.js
│ ├── datastar.js
│ ├── datastar.min.js
│ ├── htmx-extensions
│ ├── ajax-header.js
│ ├── alpine-morph.js
│ ├── class-tools.js
│ ├── client-side-templates.js
│ ├── debug.js
│ ├── disable-element.js
│ ├── event-header.js
│ ├── head-support.js
│ ├── include-vals.js
│ ├── json-enc.js
│ ├── loading-states.js
│ ├── method-override.js
│ ├── morphdom-swap.js
│ ├── multi-swap.js
│ ├── path-deps.js
│ ├── path-params.js
│ ├── preload.js
│ ├── remove-me.js
│ ├── response-targets.js
│ ├── restored.js
│ ├── sse.js
│ └── ws.js
│ ├── htmx.js
│ ├── htmx.min.js
│ └── index.html
├── composer.json
├── composer.lock
├── hypermedia
├── alpine-ajax-demo.hm.php
├── datastar-demo.hm.php
├── demos-index.hm.php
├── htmx-demo.hm.php
├── index.html
└── noswap
│ ├── alpine-ajax-demo.hm.php
│ ├── datastar-demo.hm.php
│ ├── htmx-demo.hm.php
│ └── index.html
├── includes
├── helpers.php
└── index.html
├── index.html
├── package-lock.json
├── package.json
├── src
├── Admin
│ ├── Activation.php
│ ├── Options.php
│ ├── WPSettingsOptions.php
│ └── index.html
├── Assets.php
├── Compatibility.php
├── Config.php
├── Main.php
├── Render.php
├── Router.php
├── Theme.php
└── index.html
├── uninstall.php
└── vendor
├── autoload.php
└── composer
├── ClassLoader.php
├── InstalledVersions.php
├── LICENSE
├── autoload_classmap.php
├── autoload_files.php
├── autoload_namespaces.php
├── autoload_psr4.php
├── autoload_real.php
├── autoload_static.php
├── installed.json
├── installed.php
└── platform_check.php
/.ci/update-libraries.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | 'htmx.min.js',
126 | 'hyperscript' => '_hyperscript.min.js',
127 | 'alpinejs' => 'alpinejs.min.js',
128 | 'alpine_ajax' => 'alpine-ajax.min.js',
129 | 'datastar' => 'datastar.min.js',
130 | ];
131 |
132 | $filename = $filename_map[$name] ?? "$name.min.js";
133 | $output_path = ASSETS_DIR . '/' . $filename;
134 |
135 | download_file($url, $output_path);
136 | }
137 |
138 | /**
139 | * Download HTMX extension
140 | */
141 | function download_extension($name, $url) {
142 | echo "\n🔌 Downloading HTMX extension: $name\n";
143 |
144 | ensure_dir(EXTENSIONS_DIR);
145 |
146 | $filename = "$name.js";
147 | $output_path = EXTENSIONS_DIR . '/' . $filename;
148 |
149 | download_file($url, $output_path);
150 | }
151 |
152 | /**
153 | * Parse command line arguments
154 | */
155 | function parse_args($argv) {
156 | $target_library = null;
157 |
158 | for ($i = 1; $i < count($argv); $i++) {
159 | $arg = $argv[$i];
160 |
161 | if ($arg === '--all') {
162 | $target_library = 'all';
163 | } elseif (strpos($arg, '--library=') === 0) {
164 | $target_library = substr($arg, 10);
165 | } elseif ($arg === '--library' && isset($argv[$i + 1])) {
166 | $target_library = $argv[$i + 1];
167 | $i++; // Skip next argument
168 | }
169 | }
170 |
171 | return $target_library;
172 | }
173 |
174 | /**
175 | * Main download function
176 | */
177 | function download_libraries($target_library = null) {
178 | try {
179 | echo "🔍 Getting CDN URLs...\n";
180 |
181 | $cdn_urls = get_cdn_urls();
182 | $core_count = count($cdn_urls) - (isset($cdn_urls['htmx_extensions']) ? 1 : 0);
183 | $extensions_count = isset($cdn_urls['htmx_extensions']) ? count($cdn_urls['htmx_extensions']) : 0;
184 |
185 | echo "✅ Found $core_count core libraries and $extensions_count HTMX extensions\n";
186 |
187 | if ($target_library === 'htmx-extensions') {
188 | // Download all HTMX extensions
189 | echo "\n🚀 Downloading all HTMX extensions...\n";
190 | if (isset($cdn_urls['htmx_extensions'])) {
191 | foreach ($cdn_urls['htmx_extensions'] as $name => $config) {
192 | download_extension($name, $config['url']);
193 | }
194 | }
195 | } elseif ($target_library && $target_library !== 'all') {
196 | // Download specific library
197 | if (isset($cdn_urls[$target_library])) {
198 | download_core_library($target_library, $cdn_urls[$target_library]['url']);
199 | } else {
200 | throw new Exception("Library '$target_library' not found in CDN URLs");
201 | }
202 | } else {
203 | // Download all libraries
204 | echo "\n🚀 Downloading all libraries...\n";
205 |
206 | // Download core libraries
207 | foreach ($cdn_urls as $name => $config) {
208 | if ($name !== 'htmx_extensions') {
209 | download_core_library($name, $config['url']);
210 | }
211 | }
212 |
213 | // Download HTMX extensions
214 | if (isset($cdn_urls['htmx_extensions'])) {
215 | foreach ($cdn_urls['htmx_extensions'] as $name => $config) {
216 | download_extension($name, $config['url']);
217 | }
218 | }
219 | }
220 |
221 | echo "\n🎉 All downloads completed successfully!\n";
222 |
223 | } catch (Exception $e) {
224 | echo "\n❌ Error during download: " . $e->getMessage() . "\n";
225 | exit(1);
226 | }
227 | }
228 |
229 | // Main execution
230 | if (php_sapi_name() === 'cli') {
231 | echo "🔽 HTMX API WordPress Plugin - Library Downloader (PHP)\n";
232 | echo "====================================================\n\n";
233 |
234 | $target_library = parse_args($argv);
235 |
236 | if ($target_library) {
237 | echo "🎯 Target: " . ($target_library === 'all' ? 'All libraries' : $target_library) . "\n";
238 | } else {
239 | echo "🎯 Target: All libraries (default)\n";
240 | }
241 |
242 | download_libraries($target_library);
243 | }
244 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | # WordPress Coding Standards
5 | # https://make.wordpress.org/core/handbook/coding-standards/
6 |
7 | root = true
8 |
9 | [*]
10 | charset = utf-8
11 | end_of_line = lf
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 | indent_style = space
15 |
16 | [*.yml]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
23 | [{*.txt,wp-config-sample.php}]
24 | end_of_line = crlf
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ./wp-org-assets/htmx-api-wp-logo.psd
2 | /node_modules/
3 | deploy.sh
4 | deploy-notag.sh
5 | TODO.md
6 | .wp-org-assets/htmx-api-wp-logo.psd
7 | rules-wordpress.md
8 | .php-cs-fixer.cache
9 | vendor/
10 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | true,
12 | '@PER-CS' => true,
13 | '@PHP82Migration' => true,
14 | 'array_syntax' => ['syntax' => 'short'],
15 | 'binary_operator_spaces' => [
16 | 'default' => 'single_space',
17 | 'operators' => ['=>' => null]
18 | ],
19 | 'blank_line_after_namespace' => true,
20 | 'blank_line_after_opening_tag' => true,
21 | 'blank_line_before_statement' => [
22 | 'statements' => ['return']
23 | ],
24 | 'single_space_around_construct' => true,
25 | 'control_structure_braces' => true,
26 | 'braces_position' => true,
27 | 'control_structure_continuation_position' => true,
28 | 'declare_parentheses' => true,
29 | 'statement_indentation' => true,
30 | 'no_multiple_statements_per_line' => true,
31 | 'cast_spaces' => true,
32 | 'class_attributes_separation' => [
33 | 'elements' => [
34 | 'method' => 'one',
35 | 'trait_import' => 'none'
36 | ]
37 | ],
38 | 'class_definition' => true,
39 | 'concat_space' => [
40 | 'spacing' => 'one'
41 | ],
42 | 'declare_equal_normalize' => true,
43 | 'elseif' => true,
44 | 'encoding' => true,
45 | 'full_opening_tag' => true,
46 | 'fully_qualified_strict_types' => true,
47 | 'function_declaration' => true,
48 | 'type_declaration_spaces' => true,
49 | 'heredoc_to_nowdoc' => true,
50 | 'include' => true,
51 | 'increment_style' => ['style' => 'post'],
52 | 'indentation_type' => true,
53 | 'linebreak_after_opening_tag' => true,
54 | 'line_ending' => true,
55 | 'lowercase_cast' => true,
56 | 'constant_case' => true,
57 | 'lowercase_keywords' => true,
58 | 'lowercase_static_reference' => true,
59 | 'magic_method_casing' => true,
60 | 'magic_constant_casing' => true,
61 | 'method_argument_space' => true,
62 | 'native_function_casing' => true,
63 | 'no_alias_functions' => true,
64 | 'no_extra_blank_lines' => [
65 | 'tokens' => [
66 | 'extra',
67 | 'throw',
68 | 'use'
69 | ]
70 | ],
71 | 'no_blank_lines_after_class_opening' => true,
72 | 'no_blank_lines_after_phpdoc' => true,
73 | 'no_closing_tag' => true,
74 | 'no_empty_phpdoc' => true,
75 | 'no_empty_statement' => true,
76 | 'no_leading_import_slash' => true,
77 | 'no_leading_namespace_whitespace' => true,
78 | 'no_mixed_echo_print' => [
79 | 'use' => 'echo'
80 | ],
81 | 'no_multiline_whitespace_around_double_arrow' => true,
82 | 'multiline_whitespace_before_semicolons' => [
83 | 'strategy' => 'no_multi_line'
84 | ],
85 | 'no_short_bool_cast' => true,
86 | 'no_singleline_whitespace_before_semicolons' => true,
87 | 'no_spaces_after_function_name' => true,
88 | 'no_spaces_around_offset' => true,
89 | 'spaces_inside_parentheses' => true,
90 | 'no_trailing_comma_in_singleline' => true,
91 | 'no_trailing_whitespace' => true,
92 | 'no_trailing_whitespace_in_comment' => true,
93 | 'no_unneeded_control_parentheses' => true,
94 | 'no_unreachable_default_argument_value' => true,
95 | 'no_useless_return' => true,
96 | 'no_whitespace_before_comma_in_array' => true,
97 | 'no_whitespace_in_blank_line' => true,
98 | 'normalize_index_brace' => true,
99 | 'not_operator_with_successor_space' => false,
100 | 'object_operator_without_whitespace' => true,
101 | 'ordered_imports' => ['sort_algorithm' => 'alpha'],
102 | 'phpdoc_indent' => true,
103 | 'general_phpdoc_tag_rename' => true,
104 | 'phpdoc_inline_tag_normalizer' => true,
105 | 'phpdoc_tag_type' => true,
106 | 'phpdoc_no_access' => true,
107 | 'phpdoc_no_package' => true,
108 | 'phpdoc_no_useless_inheritdoc' => true,
109 | 'phpdoc_scalar' => true,
110 | 'phpdoc_single_line_var_spacing' => true,
111 | 'phpdoc_summary' => true,
112 | 'phpdoc_to_comment' => true,
113 | 'phpdoc_trim' => true,
114 | 'phpdoc_types' => true,
115 | 'phpdoc_var_without_name' => true,
116 | 'psr_autoloading' => true,
117 | 'self_accessor' => true,
118 | 'short_scalar_cast' => true,
119 | 'simplified_null_return' => false,
120 | 'single_blank_line_at_eof' => true,
121 | //'single_blank_line_before_namespace' => true,
122 | 'single_class_element_per_statement' => true,
123 | 'single_import_per_statement' => true,
124 | 'single_line_after_imports' => true,
125 | 'single_line_comment_style' => [
126 | 'comment_types' => ['hash']
127 | ],
128 | 'single_quote' => true,
129 | 'space_after_semicolon' => true,
130 | 'standardize_not_equals' => true,
131 | 'switch_case_semicolon_to_colon' => true,
132 | 'switch_case_space' => true,
133 | 'ternary_operator_spaces' => true,
134 | 'trailing_comma_in_multiline' => true,
135 | 'trim_array_spaces' => true,
136 | 'unary_operator_spaces' => true,
137 | 'visibility_required' => [
138 | 'elements' => ['method', 'property']
139 | ],
140 | 'whitespace_after_comma_in_array' => true,
141 | 'no_unused_imports' => true,
142 | ];
143 |
144 | $finder = PhpCsFixer\Finder::create()
145 | ->exclude([
146 | '.wp-org-assets',
147 | 'vendor',
148 | 'node_modules',
149 | 'assets/js/libs',
150 | 'languages'
151 | ])
152 | ->name('*.php')
153 | ->notName([
154 | 'composer.lock',
155 | '*.js',
156 | '*.css'
157 | ])
158 | ->ignoreDotFiles(true)
159 | ->in([
160 | __DIR__ . '/src',
161 | __DIR__ . '/includes',
162 | __DIR__ . '/hypermedia'
163 | ]);
164 |
165 | return (new PhpCsFixer\Config())
166 | ->setFinder($finder)
167 | ->setRules($rules)
168 | ->setRiskyAllowed(true)
169 | ->setUsingCache(true)
170 | ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect());
171 |
--------------------------------------------------------------------------------
/.wp-org-assets/HTMX-logo-1600x1600.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/HTMX-logo-1600x1600.jpg
--------------------------------------------------------------------------------
/.wp-org-assets/banner-1544x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/banner-1544x500.png
--------------------------------------------------------------------------------
/.wp-org-assets/banner-772x250.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/banner-772x250.png
--------------------------------------------------------------------------------
/.wp-org-assets/icon-1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/icon-1024x1024.png
--------------------------------------------------------------------------------
/.wp-org-assets/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/icon-128x128.png
--------------------------------------------------------------------------------
/.wp-org-assets/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/icon-256x256.png
--------------------------------------------------------------------------------
/.wp-org-assets/screenshot-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EstebanForge/HTMX-API-WP/28205b850d56eb80a6c61dfc8fbd8a69a8375221/.wp-org-assets/screenshot-1.jpg
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | # 2.0.0 / 2025-06-06
4 | - Renamed plugin to "Hypermedia API for WordPress" to reflect broader support for multiple hypermedia libraries.
5 | - **NEW:** Added support for Datastar.js hypermedia library.
6 | - **NEW:** Added support for Alpine Ajax hypermedia library.
7 | - **NEW:** Template engine now supports both `.hm.php` (primary) and `.htmx.php` (legacy) extensions.
8 | - **NEW:** Template engine now supports both `hypermedia` (primary) and `htmx-templates` (legacy) theme directories.
9 | - **NEW:** Added `hmapi_get_endpoint_url()` helper function to get the API endpoint URL.
10 | - **NEW:** Added `hmapi_enpoint_url()` helper function to echo the API endpoint URL in templates.
11 | - **IMPROVED:** Enhanced admin interface with a new informational card displaying the API endpoint URL.
12 | - **IMPROVED:** The `$hmvals` variable is now available in templates, containing the request parameters.
13 | - **BACKWARD COMPATIBILITY:** All `hxwp_*` functions are maintained as deprecated aliases for `hmapi_*` functions.
14 | - **BACKWARD COMPATIBILITY:** The legacy `$hxvals` variable is still available in templates for backward compatibility.
15 | - **BACKWARD COMPATIBILITY:** Dual nonce system supports both `hmapi_nonce` (new) and `hxwp_nonce` (legacy).
16 | - **BACKWARD COMPATIBILITY:** Legacy filter hooks (`hxwp/`) are preserved alongside new `hmapi/` prefixed filters.
17 | - **BACKWARD COMPATIBILITY:** The plugin now intelligently sends the correct nonce with the request header, ensuring compatibility with legacy themes.
18 | - **DOCUMENTATION:** Updated `README.md` and inline documentation to reflect the latest changes.
19 |
20 | # 1.3.0 / 2025-05-11
21 | - Updated HTMX, HTMX extensions, Hyperscript and Alpine.js to their latest versions.
22 | - Added the ability to use this plugin as a library, using composer. This allows you to use HTMX in your own plugins or themes, without the need to install this plugin. The plugin/library will determine if a greater instance of itself is already loaded. If so, it will use that instance. Otherwise, it will load a new one. So, no issues with multiple instances of the same library on different plugins or themes.
23 | - Added a new way to load different HTMX templates path, using the filter `hxwp/register_template_path`. This allows you to register a new template path for your plugin or theme, without overriding the default template path, or stepping on the toes of other plugins or themes that may be using the same template path. This is useful if you want to use HTMX in your own plugin or theme, without having to worry about conflicts with other plugins or themes.
24 | - Introduced a colon (`:`) as the explicit separator for namespaced template paths (e.g., `my-plugin:path/to/template`). This provides a clear distinction between plugin/theme-specific templates and templates in the active theme's default `htmx-templates` directory. Requests for explicitly namespaced templates that are not found will result in a 404 and will not fall back to the theme's default directory.
25 | - Now using PSR-4 autoloading for the plugin's code.
26 |
27 | # 1.0.0 / 2024-08-25
28 | - Promoted plugin to stable :)
29 | - Updated to HTMX and its extensions.
30 | - Added a helper to validate requests. Automatically handles nonce and action validation.
31 |
32 | # 0.9.1 / 2024-07-05
33 | - Released on WordPress.org official plugins repository.
34 |
35 | # 0.9.0 / 2024-06-30
36 | - Updated to HTMX 2.0.0
37 | - More WP.org plugin guidelines compliance.
38 |
39 | # 0.3.2 / 2024-05-26
40 | - More WP.org plugin guidelines compliance.
41 |
42 | # 0.3.1 / 2024-05-15
43 | - Fixed a bug in the wp_localize_script() call. Thanks @mwender for the report.
44 |
45 | # 0.3.0 / 2024-05-07
46 | - WP.org plugin guidelines compliance.
47 | - Changed hxwp_send_header_response() behavior to include a nonce by default. First argument is the nonce. Second argument, an array with the data. Check the htmx-demo.htmx.php template for an updated example.
48 |
49 | # 0.2.0 / 2024-04-26
50 | - Added [Alpine.js](https://alpinejs.dev/) support. Now you can use HTMX with Alpine.js, Hyperscript, or both.
51 |
52 | # 0.1.15 / 2024-04-13
53 | - Fixes sanitization for form elements that allows multiple values. Thanks @mwender for the report. [Discussion #8](https://github.com/EstebanForge/HTMX-API-WP/discussions/8).
54 |
55 | # 0.1.14 / 2024-03-06
56 | - Added option to add the `hx-boost` (true) attribute to any enabled theme, automatically. This enables HTMX's boost feature, globally. Learn more [here](https://htmx.org/attributes/hx-boost/).
57 |
58 | # 0.1.12 / 2024-02-22
59 | - Added Composer support. Thanks @mwender!
60 | - Fixed a bug on how the plugin obtains the active theme path. Thanks again @mwender for the report and fix :)
61 | - Added a filter to allow the user to change the default path for the HTMX templates. Thanks @mwender for the suggestion.
62 |
63 | # 0.1.11 / 2024-02-21
64 | - Added WooCommerce compatibility. Thanks @carlosromanxyz for the suggestion.
65 |
66 | # 0.1.10 / 2024-02-20
67 | - Added a showcase/demo theme to demonstrate how to use HTMX with WordPress. The theme is available at [EstebanForge/HTMX-WordPress-Theme](https://github.com/EstebanForge/HTMX-WordPress-Theme).
68 | - hxwp_api_url() helper now accepts a path to be appended to the API URL. Just like WP's home_url().
69 | - Keeps line breaks on sanitization of hxvals. Thanks @texorama!
70 | - Added option to enable HTMX to load at the WordPress backend (wp-admin). Thanks @texorama for the suggestion.
71 |
72 | # 0.1.8 / 2024-02-14
73 | - HTMX and Hyperscript are now retrieved using NPM.
74 | - Fixes loading extensions from local/CDN and their paths. Thanks @agencyhub!
75 |
76 | # 0.1.7 / 2023-12-27
77 | - Bugfixes.
78 |
79 | # 0.1.6 / 2023-12-18
80 | - Merged `noswap/` folder into `htmx-templates/` folder. Now, all templates are inside `htmx-templates/` folder.
81 |
82 | # 0.1.5 / 2023-12-15
83 | - Renamed `hxparams` to `hxvals` to match HTMX terminology.
84 | - Added hxwp_die() function to be used on templates (`noswap/` included). This functions will die() the script, but sending a 200 status code so HTMX can process the response and along with a header HX-Error on it, with the message included, so it can be used on the client side.
85 |
86 | # 0.1.4 / 2023-12-13
87 | - Renamed `void/` endpoint to `noswap/` to match HTMX terminology, better showing the purpose of this endpoint.
88 | - Better path sanitization for template files.
89 | - Added `hxwp_send_header_response` function to send a Response Header back to the client, to allow for non-visual responses (`noswap/`) to execute some logic on the client side. Refer to the [Response Headers](https://htmx.org/docs/#response-headers) and [HX-Trigger](https://htmx.org/headers/hx-trigger/) sections to know more about this.
90 |
91 | # 0.1.3 / 2023-12-04
92 | - Added filters and actions to inject HTMX meta tag configuration. Refer to the [documentation](https://htmx.org/docs/#config) for more information.
93 | - Added new endpoint to wp-htmx to allow non visual responses to be executed, vía /void/ endpoint.
94 |
95 | # 0.1.1 / 2023-12-01
96 | - First public release.
97 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 | ## Why? Why HTMX? Why?!
4 |
5 | [Because...](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExM21yeDBzZ2ltcWNlZm05bjc2djF2bHo2cWVpOXcxNmQyZDJiZDhiZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/qkJJRL9Sz1R04/giphy.gif)
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hypermedia API for WordPress
2 |
3 | An unofficial WordPress plugin that enables the use of [HTMX](https://htmx.org), [Alpine AJAX](https://alpine-ajax.js.org/), [Datastar](https://data-star.dev/) and other hypermedia libraries on your WordPress site, theme, and/or plugins. Intended for software developers.
4 |
5 | Adds a new endpoint `/wp-html/v1/` from which you can load any hypermedia template.
6 |
7 |
8 |
9 | [](https://www.youtube.com/watch?v=6mrRA5QIcRw "Hypermedia API for WordPress Demo")
10 |
11 |
12 |
13 | [Check the video](https://www.youtube.com/watch?v=6mrRA5QIcRw)
14 |
15 |
16 |
17 |
18 |
19 | ## Hypermedia what?
20 |
21 | [Hypermedia](https://hypermedia.systems/) is a "new" concept that allows you to build modern web applications, even SPAs, without the need to write a single line of JavaScript. A forgotten concept that was popular in the 90s and early 2000s, but has been forgotten by newer generations of software developers.
22 |
23 | HTMX, Alpine Ajax and Datastar are JavaScript libraries that allows you to access AJAX, WebSockets, and Server-Sent Events directly in HTML using attributes, without writing any JavaScript.
24 |
25 | Unless you're trying to build a Google Docs clone or a competitor, Hypermedia allows you to build modern web applications, even SPAs, without the need to write a single line of JavaScript.
26 |
27 | For a better explanation and demos, check the following video:
28 |
29 |
30 |
31 | [](https://www.youtube.com/watch?v=Fuz-jLIo2g8)
32 |
33 |
34 |
35 | ## Why mix it with WordPress?
36 |
37 | Because I share the same sentiment as Carson Gross, the creator of HTMX, that the software stack used to build the web today has become too complex without good reason (most of the time). And, just like him, I also want to see the world burn.
38 |
39 | (Seriously) Because Hypermedia is awesome, and WordPress is awesome (sometimes). So, why not?
40 |
41 | I'm using this in production for a few projects, and it's working great, stable, and ready to use. So, I decided to share it with the world.
42 |
43 | I took this idea out of the tangled mess it was inside a project and made it into a standalone plugin that should work for everyone.
44 |
45 | It might have some bugs, but the idea is to open it up and improve it over time.
46 |
47 | So, if you find any bugs, please report them.
48 |
49 | ## Installation
50 |
51 | Install it directly from the WordPress.org plugin repository. On the plugins install page, search for: Hypermedia API
52 |
53 | Or download the zip from the [official plugin repository](https://wordpress.org/plugins/api-for-htmx/) and install it from your WordPress plugins install page.
54 |
55 | Activate the plugin. Configure it to your liking on Settings > Hypermedia API.
56 |
57 | ### Installation via Composer
58 | If you want to use this plugin as a library, you can install it via Composer. This allows you to use hypermedia libraries in your own plugins or themes, without the need to install this plugin.
59 |
60 | ```bash
61 | composer require estebanforge/hypermedia-api-wordpress
62 | ```
63 |
64 | This plugin/library will determine which instance of itself is the newer one when WordPress is loading. Then, it will use the newer instance between all competing plugins or themes. This is to avoid conflicts with other plugins or themes that may be using the same library for their Hypermedia implementation.
65 |
66 | ## How to use
67 |
68 | After installation, you can use hypermedia templates in any theme.
69 |
70 | This plugin will include the active hypermedia library by default, locally from the plugin folder. Libraries like HTMX, Alpine.js, Hyperscript, and Datastar are supported.
71 |
72 | The plugin has an opt-in option, not enforced, to include these third-party libraries from a CDN (using the unpkg.com service). You must explicitly enable this option for privacy and security reasons.
73 |
74 | Create a `hypermedia` folder in your theme's root directory. This plugin includes a demo folder that you can copy to your theme. Don't put your templates inside the demo folder located in the plugin's directory, because it will be deleted when you update the plugin.
75 |
76 | Inside your `hypermedia` folder, create as many templates as you want. All files must end with `.hm.php`.
77 |
78 | For example:
79 |
80 | ```
81 | hypermedia/live-search.hm.php
82 | hypermedia/related-posts.hm.php
83 | hypermedia/private/author.hm.php
84 | hypermedia/private/author-posts.hm.php
85 | ```
86 |
87 | Check the demo template at `hypermedia/demo.hm.php` to see how to use it.
88 |
89 | Then, in your theme, use your Hypermedia library to GET/POST to the `/wp-html/v1/` endpoint corresponding to the template you want to load, without the file extension:
90 |
91 | ```
92 | /wp-html/v1/live-search
93 | /wp-html/v1/related-posts
94 | /wp-html/v1/private/author
95 | /wp-html/v1/private/author-posts
96 | ```
97 |
98 | ### Helper Functions
99 |
100 | You can use the `hmapi_get_endpoint_url()` helper function to generate the URL for your hypermedia templates. This function will automatically add the `/wp-html/v1/` prefix. The hypermedia file extension (`.hm.php`) is not needed, the API will resolve it automatically.
101 |
102 | For example:
103 |
104 | ```php
105 | echo hmapi_get_endpoint_url( 'live-search' );
106 | ```
107 |
108 | Or,
109 |
110 | ```php
111 | hmapi_endpoint_url( 'live-search' );
112 | ```
113 |
114 | Will call the template located at:
115 |
116 | ```
117 | /hypermedia/live-search.hm.php
118 | ```
119 | And will load it from the URL:
120 |
121 | ```
122 | http://your-site.com/wp-html/v1/live-search
123 | ```
124 |
125 | This will output:
126 |
127 | ```
128 | http://your-site.com/wp-html/v1/live-search
129 | ```
130 |
131 | #### Backward Compatibility
132 |
133 | For backward compatibility, the old `hxwp_api_url()` function is still available as an alias for `hmapi_get_endpoint_url()`. However, we recommend updating your code to use the new function names as the old ones are deprecated and may be removed in future versions.
134 |
135 | Other helper functions available:
136 | - `hmapi_send_header_response()` / `hxwp_send_header_response()` (deprecated alias)
137 | - `hmapi_die()` / `hxwp_die()` (deprecated alias)
138 | - `hmapi_validate_request()` / `hxwp_validate_request()` (deprecated alias)
139 |
140 | ### How to pass data to the template
141 |
142 | You can pass data to the template using URL parameters (GET/POST). For example:
143 |
144 | ```
145 | /wp-html/v1/live-search?search=hello
146 | /wp-html/v1/related-posts?category_id=5
147 | ```
148 |
149 | All of those parameters (with their values) will be available inside the template as an array named: `$hmvals`.
150 |
151 | ### No Swap response templates
152 |
153 | Hypermedia libraries allow you to use templates that don't return any HTML but perform some processing in the background on your server. These templates can still send a response back (using HTTP headers) if desired. Check [Swapping](https://htmx.org/docs/#swapping) for more info.
154 |
155 | For this purpose, and for convenience, you can use the `noswap/` folder/endpoint. For example:
156 |
157 | ```
158 | /wp-html/v1/noswap/save-user?user_id=5&name=John&last_name=Doe
159 | /wp-html/v1/noswap/delete-user?user_id=5
160 | ```
161 |
162 | In this examples, the `save-user` and `delete-user` templates will not return any HTML, but will do some processing in the background. They will be loaded from the `hypermedia/noswap` folder.
163 |
164 | ```
165 | hypermedia/noswap/save-user.hm.php
166 | hypermedia/noswap/delete-user.hm.php
167 | ```
168 |
169 | You can pass data to these templates in the exact same way as you do with regular templates.
170 |
171 | Nothing stops you from using regular templates to do the same thing or using another folder altogether. You can mix and match or organize your templates in any way you want. This is mentioned here just as a convenience feature for those who want to use it.
172 |
173 | ### Choosing a Hypermedia Library
174 |
175 | This plugin comes with [HTMX](https://htmx.org), [Alpine Ajax](https://alpine-ajax.js.org/) and [Datastar](https://data-star.dev/) already integrated and enabled.
176 |
177 | You can choose which library to use in the plugin's options page: Settings > Hypermedia API.
178 |
179 | In the case of HTMX, you can also enable any of its extensions in the plugin's options page: Settings > Hypermedia API.
180 |
181 | #### Local vs CDN Loading
182 |
183 | The plugin includes local copies of all libraries for privacy and offline development. You can choose to load from:
184 |
185 | 1. **Local files** (default): Libraries are served from your WordPress installation
186 | 2. **CDN**: Optional CDN loading from jsdelivr.net. Will always load the latest version of the library.
187 |
188 | #### Build System Integration
189 |
190 | For developers, the plugin includes npm scripts to download the latest versions of all libraries locally:
191 |
192 | ```bash
193 | # Download all libraries
194 | npm run download:all
195 |
196 | # Download specific library
197 | npm run download:htmx
198 | npm run download:alpine
199 | npm run download:hyperscript
200 | npm run download:datastar
201 | npm run download:all
202 | ```
203 |
204 | This ensures your local development environment stays in sync with the latest library versions.
205 |
206 | ## Using Hypermedia Libraries in your plugin
207 |
208 | You can definitely use hypermedia libraries and this Hypermedia API for WordPress in your plugin. You are not limited to using it only in your theme.
209 |
210 | The plugin provides the filter: `hmapi/register_template_path`
211 |
212 | This filter allows you to register a new template path for your plugin or theme. It expects an associative array where keys are your chosen namespaces and values are the absolute paths to your template directories.
213 |
214 | For example, if your plugin slug is `my-plugin`, you can register a new template path like this:
215 |
216 | ```php
217 | add_filter( 'hmapi/register_template_path', function( $paths ) {
218 | // Ensure YOUR_PLUGIN_PATH is correctly defined, e.g., plugin_dir_path( __FILE__ )
219 | // 'my-plugin' is the namespace.
220 | $paths['my-plugin'] = YOUR_PLUGIN_PATH . 'hypermedia/';
221 |
222 | return $paths;
223 | });
224 | ```
225 |
226 | Assuming `YOUR_PLUGIN_PATH` is already defined and points to your plugin's root directory, the above code registers the `my-plugin` namespace to point to `YOUR_PLUGIN_PATH/hypermedia/`.
227 |
228 | Then, you can use the new template path in your plugin like this, using a colon `:` to separate the namespace from the template file path (which can include subdirectories):
229 |
230 | ```php
231 | // Loads the template from: YOUR_PLUGIN_PATH/hypermedia/template-name.hm.php
232 | echo hmapi_get_endpoint_url( 'my-plugin:template-name' );
233 |
234 | // Loads the template from: YOUR_PLUGIN_PATH/hypermedia/parts/header.hm.php
235 | echo hmapi_get_endpoint_url( 'my-plugin:parts/header' );
236 | ```
237 |
238 | This will output the URL for the template from the path associated with the `my-plugin` namespace. If the namespace is not registered, or the template file does not exist within that registered path (or is not allowed due to sanitization rules), the request will result in a 404 error. Templates requested with an explicit namespace do not fall back to the theme's default `hypermedia` directory.
239 |
240 | For templates located directly in your active theme's `hypermedia` directory (or its subdirectories), you would call them without a namespace:
241 |
242 | ```php
243 | // Loads: wp-content/themes/your-theme/hypermedia/live-search.hm.php
244 | echo hmapi_get_endpoint_url( 'live-search' );
245 |
246 | // Loads: wp-content/themes/your-theme/hypermedia/subfolder/my-listing.hm.php
247 | echo hmapi_get_endpoint_url( 'subfolder/my-listing' );
248 | ```
249 |
250 | ## Security
251 |
252 | Every call to the `wp-html` endpoint will automatically check for a valid nonce. If the nonce is not valid, the call will be rejected.
253 |
254 | The nonce itself is auto-generated and added to all HTMX requests automatically, using HTMX's own `htmx:configRequest` event.
255 |
256 | If you are new to Hypermedia, please read the [security section](https://htmx.org/docs/#security) of the official documentation. Remember that Hypermedia requires you to validate and sanitize any data you receive from the user. This is something developers used to do all the time, but it seems to have been forgotten by newer generations of software developers.
257 |
258 | If you are not familiar with how WordPress recommends handling data sanitization and escaping, please read the [official documentation](https://developer.wordpress.org/themes/theme-security/data-sanitization-escaping/) on [Sanitizing Data](https://developer.wordpress.org/apis/security/sanitizing/) and [Escaping Data](https://developer.wordpress.org/apis/security/escaping/).
259 |
260 | ### REST Endpoint
261 |
262 | The plugin will perform basic sanitization of calls to the new REST endpoint, `wp-html`, to avoid security issues like directory traversal attacks. It will also limit access so you can't use it to access any file outside the `hypermedia` folder within your own theme.
263 |
264 | The parameters and their values passed to the endpoint via GET or POST will be sanitized with `sanitize_key()` and `sanitize_text_field()`, respectively.
265 |
266 | Filters `hmapi/sanitize_param_key` and `hmapi/sanitize_param_value` are available to modify the sanitization process if needed. For backward compatibility, the old filters `hxwp/sanitize_param_key` and `hxwp/sanitize_param_value` are still supported but deprecated.
267 |
268 | Do your due diligence and ensure you are not returning unsanitized data back to the user or using it in a way that could pose a security issue for your site. Hypermedia requires that you validate and sanitize any data you receive from the user. Don't forget that.
269 |
270 | ## Examples
271 |
272 | Check out the showcase/demo theme at [EstebanForge/Hypermedia-Theme-WordPress](https://github.com/EstebanForge/Hypermedia-Theme-WordPress).
273 |
274 | ## Suggestions, Support
275 |
276 | Please, open [a discussion](https://github.com/EstebanForge/hypermedia-api-wordpress/discussions).
277 |
278 | ## Bugs and Error reporting
279 |
280 | Please, open [an issue](https://github.com/EstebanForge/hypermedia-api-wordpress/issues).
281 |
282 | ## FAQ
283 | [FAQ available here](https://github.com/EstebanForge/hypermedia-api-wordpress/blob/main/FAQ.md).
284 |
285 | ## Changelog
286 |
287 | [Changelog available here](https://github.com/EstebanForge/hypermedia-api-wordpress/blob/main/CHANGELOG.md).
288 |
289 | ## Contributing
290 |
291 | You are welcome to contribute to this plugin.
292 |
293 | If you have a feature request or a bug report, please open an issue on the [GitHub repository](https://github.com/EstebanForge/hypermedia-api-wordpress/issues).
294 |
295 | If you want to contribute with code, please open a pull request.
296 |
297 | ## License
298 |
299 | This plugin is licensed under the GPLv2 or later.
300 |
301 | You can find the full license text in the `license.txt` file.
302 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | === API for HTMX ===
2 | Contributors: tcattd
3 | Tags: htmx, ajax, hypermedia, hyperscript, alpinejs
4 | Stable tag: 2.0.0
5 | Requires at least: 6.4
6 | Tested up to: 6.6
7 | Requires PHP: 8.1
8 | License: GPLv2 or later
9 | License URI: http://www.gnu.org/licenses/gpl-2.0.txt
10 |
11 | An unofficial WordPress plugin that enables the use of Hypermedia on your WordPress site, theme, and/or plugins. Intended for software developers.
12 |
13 | == Description ==
14 | An unofficial WordPress plugin that enables the use of Hypermedia on WordPress. Adds a new endpoint `/wp-htmx/v1/` from which you can load any Hypermedia template.
15 |
16 | Hypermedia is a concept that extends the idea of hypertext by allowing for more complex interactions and data representations. It enables the use of AJAX, WebSockets, and Server-Sent Events directly in HTML using attributes, without writing any JavaScript. It reuses an "old" concept, [Hypermedia](https://hypermedia.systems/), to handle the modern web in a more HTML-like and natural way.
17 |
18 | Check the [full feature set at here](https://github.com/EstebanForge/Hypermedia-API-WordPress).
19 |
20 | This plugin include several Hypermedia libraries by default, locally from the plugin folder. Currently, it includes:
21 |
22 | - [HTMX](https://htmx.org/) with [Hyperscript](https://hyperscript.org/).
23 | - [Alpine Ajax](https://alpine-ajax.js.org/) with [Alpine.js](https://alpinejs.dev/).
24 | - [Datastar](https://data-star.dev/).
25 |
26 | The plugin has an opt-in option, not enforced, to include these third-party libraries from a CDN (using the unpkg.com service). You must explicitly enable this option for privacy and security reasons.
27 |
28 | == Installation ==
29 | 1. Install Hypermedia-API-WordPress from WordPress repository. Plugins > Add New > Search for: Hypermedia API or API-for-HTMX. Activate it.
30 | 2. Configure Hypermedia-API-WordPress at Settings > Hypermedia API.
31 | 3. Enjoy.
32 |
33 | == Frequently Asked Questions ==
34 | = Where is the FAQ? =
35 | You can [read the full FAQ at GitHub](https://github.com/EstebanForge/Hypermedia-API-WordPress/blob/main/FAQ.md).
36 |
37 | = Suggestions, Support? =
38 | Please, open [a discussion](https://github.com/EstebanForge/Hypermedia-API-WordPress/discussions).
39 |
40 | = Found a Bug or Error? =
41 | Please, open [an issue](https://github.com/EstebanForge/Hypermedia-API-WordPress/issues).
42 |
43 | == Screenshots ==
44 | 1. Main options page.
45 |
46 | == Upgrade Notice ==
47 | Nothing to see here.
48 |
49 | == Changelog ==
50 | [Check the changelog at GitHub](https://github.com/EstebanForge/Hypermedia-API-WordPress/blob/master/CHANGELOG.md).
51 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | | Version | Supported |
6 | | ------- | ------------------ |
7 | | 2.0.0 | :white_check_mark: |
8 | | <2.0.0 | :x: |
9 |
10 | ## Reporting a Vulnerability
11 |
12 | Please, contact me at any of the following email addresses:
13 |
14 | esteban at attitude.cl
15 |
16 | esteban at actitud.xyz
17 |
18 | Thanks!
19 |
--------------------------------------------------------------------------------
/api-for-htmx.php:
--------------------------------------------------------------------------------
1 | 'Version'], false);
28 | $current_hmapi_instance_version = $hmapi_plugin_data['Version'] ?? '0.0.0'; // Default to 0.0.0 if not found
29 | $current_hmapi_instance_path = realpath(__FILE__);
30 |
31 | // Register this instance as a candidate
32 | // Globals, i know. But we need a fast way to do this.
33 | if (!isset($GLOBALS['hmapi_api_candidates']) || !is_array($GLOBALS['hmapi_api_candidates'])) {
34 | $GLOBALS['hmapi_api_candidates'] = [];
35 | }
36 |
37 | // Use path as key to prevent duplicates from the same file if included multiple times
38 | $GLOBALS['hmapi_api_candidates'][$current_hmapi_instance_path] = [
39 | 'version' => $current_hmapi_instance_version,
40 | 'path' => $current_hmapi_instance_path,
41 | 'init_function' => 'hmapi_run_initialization_logic',
42 | ];
43 |
44 | // Hook to decide and run the winner. This action should only be added once.
45 | if (!has_action('plugins_loaded', 'hmapi_select_and_load_latest')) {
46 | add_action('plugins_loaded', 'hmapi_select_and_load_latest', 0); // Priority 0 to run very early
47 | }
48 |
49 | /*
50 | * Contains the actual plugin initialization logic.
51 | * This function is called only for the winning (latest version) instance.
52 | *
53 | * @param string $plugin_file_path Path to the plugin file that should run.
54 | * @param string $plugin_version The version of the plugin file.
55 | */
56 | if (!function_exists('hmapi_run_initialization_logic')) {
57 | function hmapi_run_initialization_logic(string $plugin_file_path, string $plugin_version): void
58 | {
59 | // These constants signify that the chosen instance is now loading.
60 | define('HMAPI_INSTANCE_LOADED', true);
61 | define('HMAPI_LOADED_VERSION', $plugin_version);
62 | define('HMAPI_INSTANCE_LOADED_PATH', $plugin_file_path);
63 |
64 | // Define plugin constants using the provided path and version
65 | define('HMAPI_VERSION', $plugin_version);
66 | define('HMAPI_ABSPATH', plugin_dir_path($plugin_file_path));
67 | define('HMAPI_BASENAME', plugin_basename($plugin_file_path));
68 | define('HMAPI_PLUGIN_URL', plugin_dir_url($plugin_file_path));
69 | define('HMAPI_PLUGIN_FILE', $plugin_file_path);
70 | define('HMAPI_ENDPOINT', 'wp-html'); // New primary endpoint
71 | define('HMAPI_LEGACY_ENDPOINT', 'wp-htmx');
72 | define('HMAPI_TEMPLATE_DIR', 'hypermedia'); // Default template directory in theme
73 | define('HMAPI_LEGACY_TEMPLATE_DIR', 'htmx-templates'); // Legacy template directory in theme
74 | define('HMAPI_TEMPLATE_EXT', '.hm.php'); // Default template file extension
75 | define('HMAPI_LEGACY_TEMPLATE_EXT', '.htmx.php'); // Legacy template file extension
76 | define('HMAPI_ENDPOINT_VERSION', 'v1');
77 |
78 | // --- Backward Compatibility Aliases for Constants ---
79 | if (!defined('HXWP_VERSION')) {
80 | define('HXWP_VERSION', HMAPI_VERSION);
81 | define('HXWP_ABSPATH', HMAPI_ABSPATH);
82 | define('HXWP_BASENAME', HMAPI_BASENAME);
83 | define('HXWP_PLUGIN_URL', HMAPI_PLUGIN_URL);
84 | define('HXWP_ENDPOINT', HMAPI_LEGACY_ENDPOINT);
85 | define('HXWP_ENDPOINT_VERSION', HMAPI_ENDPOINT_VERSION);
86 | define('HXWP_TEMPLATE_DIR', HMAPI_TEMPLATE_DIR);
87 | }
88 | // --- End Backward Compatibility Aliases ---
89 |
90 | // Composer autoloader
91 | if (file_exists(HMAPI_ABSPATH . 'vendor/autoload.php')) {
92 | require_once HMAPI_ABSPATH . 'vendor/autoload.php';
93 | // Helpers
94 | require_once HMAPI_ABSPATH . 'includes/helpers.php';
95 | } else {
96 | // Log error or display admin notice
97 | add_action('admin_notices', function () {
98 | echo '
' . __('Hypermedia API: Composer autoloader not found. Please run "composer install" inside the plugin folder.', 'api-for-htmx') . '