Search the Community
Showing results for 'render('.
-
A simple, flexible asset management system for ProcessWire that makes handling CSS and JavaScript effortless. Organize assets with groups and collections, seamlessly switch between CDNs and local files, inline critical resources, generate integrity hashes, and render optimized HTML tags - all with an intuitive notation system and clean API that feels natural in ProcessWire. Alpha — v0.1.0. This module is in early testing. The API may change before a stable release. Feedback and bug reports are welcome.
-
I use frontend editor in some of my textarea fields. I do it for a long time, without problems. Now, after rebuilding one of my sites, frontend editor works on some pages; on other pages the field I'm trying to edit disappears (not for good, only when I'm trying to edit). On the back office editor, no problem. Did it happened to anyone else? Do you have a clue about possible causes? PW version: 3.0.255 PHP : 8.4 Edit: On a better thought, I begin to suspect that the pages where front-edit doesn't work are those where the textarea field starts with a hannacode (marked with [[...]] ). I use it some times to embed jumplinks. Edit2: I'm using TinyMCE . Edit3: Nop. I tried to render the page without jumplinks, but the problem remains.
-
Hi everyone, I've been timing my bigger releases with the lunar cycle — new moon for launches, full moon for milestones. Collections shipped on the full moon two weeks ago. Tonight the moon is invisible. The module isn't. 🌑 GitHub: https://github.com/mxmsmnv/Ichiban Why Ichiban? Ichiban (一番) is Japanese for "number one" — fitting for an SEO module whose entire purpose is to help your pages rank first. Why another SEO module? There hasn't been a comprehensive SEO module for ProcessWire for a while. Yoast and RankMath solve this for WordPress — both charge $59–$99/year for the full feature set. Ichiban is MIT, free, and built specifically for ProcessWire. What it does Page field — five-tab editor per page: Meta (Google-style SERP preview), Social (OG + Twitter/X cards), Schema, Sitemap, Advanced. Render with echo $page->seo; or enable auto-injection. Source expressions — resolve field values dynamically: title|truncate:70 field:summary|truncate:160 {splash} Admin sections: Dashboard — battery-style site score, health stats, GSC highlights, indexing issues Bulk Editor — edit all meta titles/descriptions in one table, grouped by Critical/Warnings/Healthy Audit — site-wide SEO rule checks, priority cards, CSV export, hookable rule system Redirects — 301/302/307/410/451, regex rules, hit counts, CSV import/export, auto-redirects on slug change Insights — Google Search Console OAuth, metrics, top pages/queries, countries, devices, URL Inspection scan Backlinks — Moz API snapshots, cached history, links/domains/anchors views Schemas — database-backed Schema.org builder, map properties to PW fields Revisions — tracked SEO field changes with restore Cleanup — remove low-value head tags, block spam crawl queries Migration — SeoMaestro → Ichiban converter (15 fields mapped) Reports — scheduled SEO email reports, DOCX export AI — OpenRouter-backed SEO prompt workspace with Context module integration XML Sitemap — built-in generator with hreflang, image sitemap, LazyCron auto-regeneration IndexNow — one-click key generation and verification robots.txt / llms.txt — dynamic serving (companion to RobotsTxt module) Known alpha limits GSC requires a Google OAuth client setup — not plug-and-play Moz free API quota is very small — refresh intentionally Schema Builder is alpha — test before production Auto head injection can conflict with existing theme SEO tags — use manual echo $page->seo; first SeoMaestro migration is experimental — always backup before running Disable debug mode before production Requirements: ProcessWire 3.0.200+, PHP 8.1+ MIT License — free, no Pro tier, no upsell.
- 8 replies
-
- 16
-
-
Hello, A small utility I built for my own workflow — export any page directly from the editor as a clean Markdown file. Useful for documentation, content migration, and feeding page content to AI tools. GitHub: https://github.com/mxmsmnv/PageMarkdown What it does: Adds an Export to Markdown button to the page edit form Smart HTML conversion — CKEditor content (tables, lists, headings, links, bold/italic) → standard Markdown Supports ProFields: Table, Combo, Repeater Matrix (with type labels and nested structure) Images and files render as Markdown image/link syntax Page references render as links or titles MapMarker, Email, URL, Color fields all handled Configurable: toggle field labels as headings, ignore lists per field/type, datetime format, empty HTML cleanup Requirements: ProcessWire 3.0+, PHP 8.0+ MIT License.
-
Hello everyone, We're slowly working through our site and moving things into modules for antarctica.gov.au, in the process we hope to open source some handy things we've built over the years. As we operate in Microsoft heavy corporate environment, we've noticed more and more strange looking links getting pasted into our site. These are Microsoft protected links and are a 'intercept' link that attempts to check the link is legit before letting the user proceed to the original URL. These links get automatically made when a user copies a link from Outlook/Teams/Office. The issue is, these links leak data in the URL parameters, including the email address of who has copied the link! Hence, we wrote two companion modules to find and format these links. First is TextformatterMicrosoftProtectedLinks which formats the links at render time. Second is FormatMicrosoftProtectedLinks which replaces the link with the original URL when saving the page. We provided both modules as it depends on your preference. Some people like to leave the page content as the original (including the protected link), others like to replace this. Let us know if you notice any issues using these modules.
-
- 6
-
-
Working with AI in PageGrid Inspired by projects like AgentTools, I began investigating how well an AI could handle PageGrid’s native PW structure (Pages, Templates, and Fields). The results were surprisingly good and with a few targeted optimizations, the workflow has become remarkably solid. With the latest updates, an AI agent is reliably able to create and design PageGrid layouts, create new block templates, or perform content updates. To teach AI how to "speak PageGrid", I created a small AGENTS.md file that acts as a central hub. I’ve then added specific "skills" in separate .md files for various tasks ensuring the AI only loads the documentation it actually needs. This approach is highly optimized to minimize token consumption, allowing even "smaller" models like GPT-4o mini or Claude Haiku to produce error-free migrations. To make this possible, I also introduced dedicated Migration Functions that allow the AI to programmatically add items or set styles. For CLI-based projects, I highly recommend the AgentTools Module to streamline the integration. Beyond CLI support, AgentTools also provides a native AI interface directly within the ProcessWire backend editor. Getting Started Install the lastest version of the FieldtypePageGrid module (try for free). Tell your AI agent to read the PageGrid agent guide first: That file gives the agent everything it needs to understand PageGrid and routes it to the right documentation for your task. What You Can Ask the AI to Do Build or modify a layout Create pages with blocks, apply styles, set up responsive layouts using CSS grid columns. Example prompts: "Create a landing page with a full-width hero section and a 3-column feature grid below it." "Add a text block and an image block side by side inside the first group on my homepage." "Make the hero block have a dark background and white text, with 60px padding on desktop and 24px on mobile." The core advantage here is that the AI doesn't write your frontend code from scratch. The HTML and logic are already defined in your PageGrid Block Templates. The AI simply acts as an orchestrator, assembling these blocks and applying styles. This ensures the output remains clean, semantic, and easy to maintain via drag-and-drop later on. Create a custom block template Define a new block type with custom fields and register it with a PageGrid field. Example prompts: "Create a custom block called pg_testimonial with a quote text field and an author name field." "Create a custom card block with a title, description, and link field, and add it to my homepage PageGrid field." Write a site template Render a PageGrid field inside your own PHP template file. Example prompts: "Show me how to render my PageGrid field inside my home.php template using markup regions." "Generate a site template for pagegrid-page that includes the PageGrid output between the header and footer." Docs Documentaion
-
Hello community! I want to share a new module I've been working on that I think could be a big boost for multi-language ProcessWire sites. Fluency is available in the ProcessWire Modules Directory, via Composer, and on Github Some background: I was looking for a way for our company website to be efficiently translated as working with human translators was pretty laborious and a lack of updating content created a divergence between languages. I, and several other devs here, have talked about translation integrations and the high quality services now available. Inspired by what is possible with ProcessWire, I built Fluency, a third-party translation service integration for ProcessWire. With Fluency you can: Translate any plain textarea or text input Translate any TinyMCE or CKEditor (inline, or regular) Translate page names/URLs Translate in-template translation function wrapped strings Translate modules, both core and add-ons Installation and usage is completely plug and play. Whether you're building a new multi-language site, need to update a site to multi-language, or simply want to stop manually translating a site and make any language a one-click deal, it could not be easier to do it. Fluency works by having you match the languages configured in ProcessWire to those offered by the third party translation service you choose. Currently Fluency works with DeepL and Google Cloud Translation. Module Features Translate any multilanguage field while editing any page. Translate fields in Repeater, Repeater Matrix, Table, Fieldset Page, Image descriptions, etc. Translate any file that added in the ProcessWire language pages. It's possible to translate the entire ProcessWire core in ~20 minutes Provide intuitive translation features that your clients and end-users can actually use. Fluency is designed for real-world use by individuals of all skill levels with little to no training. Its ease-of-use helps encourage users to adopt a multilanguage workflow. Start for free, use for free. Translation services supported by Fluency offer generous free tiers that can support regular usage levels. Fluency is, and will always be, free and open source. Use more than one Translation Engine. You may configure Fluency to use either DeepL, Google Cloud Translation, or both by switching between them as desired. AI powered translations that rival humans. DeepL provides the highest levels of accuracy in translation of any service available. Fluency has been used in many production sites around the world and in commercial applications where accuracy matters. Deliver impressive battle-tested translation features your clients can count on. Disable translation for individual fields. Disable translation for multilanguage fields where values aren't candidates for translation such as phone numbers or email addresses Configure translation caching. Caching can be enabled globally so that the same content translated more than once anywhere in ProcessWire doesn't count against your API usage and provides lightning fast responses. Set globally ignored words and text. Configure Fluency to add exclusionary indicators during translation so that specific words or phrases remain untranslated. This works either for specific strings alone, or present in other content while remaining grammatically correct in translation. Choose how translation is handled for fields. Configure Fluency to have buttons for either "Translate from {default language}" on each tab, or "Translate To All Languages" to populate every language for a field from any language to any language you have configured. No language limits. Configure as few or as many languages as you need. 2, 5, 10, 20 language website? Absolutely possible. If the translation service you choose offers a language, you can use it in ProcessWire. When new languages are introduced by third parties, they're ready to use in Fluency. Visually see what fields and language tabs have modified content. Fluency adds an visual indication to each field language tab to indicate which has different content than when opening the edit page. This helps ensure that content updated in one language should be updated in other languages to prevent content divergence between languages. Render language meta tags and ISO codes. Output alt language meta tags, add the current language's ISO code to your <html lang=""> attribute to your templates that are automatically generated from accurate data from the third party translation service. Build a standards-compliant multi-language SEO ready page in seconds with no additional configuration. Render language select elements. - Fluency can generate an unordered list of language links to switch between languages when viewing your pages. You can also embed a <select> element with JS baked in to switch between languages when viewing your pages. Render it without JS to use your own. Manage feature access for users. Fluency provides a permission that can be assigned to user roles for managing who can translate content. Track your translation account usage. View your current API usage, API account limit, and remaining allotment to keep an eye on and manage usage. (Currently only offered by DeepL) Use the global translation tool. Fluency provides translation on each field according to the languages you configure in ProcessWire. Use the global translation tool to translate any content to any language. Use Fluency from your templates and code. All translation features, usage statistics, cache control, and language data are accessible globally from the $fluency object. Perform any operation and get data for any language programmatically wherever you need it. Build custom AJAX powered admin translation features for yourself. Fluency provides a full RESTful API within the ProcessWire admin to allow developers to add new features for ProcessWire applications powered by the same API that Fluency uses. Robust plain-language documentation that helps you get up to speed fast. Fluency is extremely easy to use but also includes extensive documentation for all features both within the admin and for the Fluency programming API via the README.md document. The module code itself is also fully annotated for use with the ProDevTools API explorer. Is and will always be data safe. Adding, upgrading, or removing Fluency does not modify or remove your content. ProcessWire handles your data, Fluency sticks to translating. Full module localization. Translate Fluency itself to any language. All buttons, messages, and UI elements for Fluency will be presented in any language you choose for the ProcessWire admin. Built for expansion. Fluency provides translation services as modular "Translation Engines" with a full framework codebase to make adding new translation services easier and more reliable. Contributions for new translation services are welcome. Fluency is designed and built to provide everything you need to handle incredibly accurate translations and robust tools that make creating and managing multi-language sites a breeze. Built through research on translation plugins from around the web, it's the easiest and most friendly translation implementation for both end users and developers on any CMS/CMF anywhere. Fluency complements the built-in first class language features of ProcessWire. Fluency continues to be improved with great suggestions from the community and real-world use in production applications. Big thanks to everyone who has helped make Fluency better. Contributions, suggestions, and bug reports welcome! Please note that the browser plugin for Grammarly conflicts with Fluency (as it does with many web applications). To address this issue it is recommended that you disable Grammarly when using Fluency, or open the admin to edit pages in a private window where Grammarly may not be loaded. This is a long-standing issue in the larger web development community and creating a workaround may not be possible. If you have insight as to how this may be solved please visit the Github page and file a bugfix ticket. Requirements: ProcessWire 3.0+ UIKit Admin Theme That's Fluency in a nutshell. The Module Is Free This is my first real module and I want to give it back to the community as thanks. This is the best CMS I've worked with (thank you Ryan & contributors) and a great community (thank you dear reader). DeepL Developer Accounts In addition to paid Pro Developer accounts, DeepL now offers no-cost free accounts. All ProcessWire developers and users can use Fluency at no cost. Learn more about free and paid accounts by visiting the DeepL website. Sign up for a Developer account, get an API key, and start using Fluency. Download You can install Fluency by adding the module to your ProcessWire project using any of the following methods. Method 1: Within ProcessWire using 'Add Module From Directory' and the class name Fluency Method 2: Via Composer with composer require firewire/fluency Method 3: Download from the Github repository and unzip the contents into /site/modules/ Feedback File issues and feature requests here (your feedback and testing is greatly appreciated): https://github.com/SkyLundy/Fluency/issues Thank you! ¡Gracias! Ich danke Ihnen! Merci! Obrigado! Grazie! Dank u wel! Dziękuję! Спасибо! ありがとうございます! 谢谢你
- 318 replies
-
- 46
-
-
-
- translation
- language
-
(and 1 more)
Tagged with:
-
Hi, Yesterday I found out about the relatively new markup regions functionality and I love it. The ease of use and simplicity is just what I think is typically ProcessWire and is why Im still happy I chose for PW four years ago when changing CMS/CMF. So thanks Ryan for your vision and all you put into it. That said, Im running into an issue I'm not sure what to think of it. I have enabled markup regions in config.php and it does what I suppose it should: it replaced and all. Very neatly. The only thing I discovered is that while Im debugging some issues and I use var_dump to echo some stuff, it does that two times in a row. And this only happens when useMarkupRegions is enabled. I dont get this, does it mean the template file gets called twice (and thus the script is run twice?) or is it only displayed twice, where the first time most output is erased and /or replaced by the substituted output? I don't really get whats going on there. Is this behavior normal when using markup regions? Thanks!
-
Hi. I've recently moved a long standing site to a new server and now I get wireRenderFile is undefined on home page. So I checked on my localhost server and it is running fine on there. I'm using Markup Regions and the home page is just like all my pages using : <div id="ajax-content" pw-replace> <?=wireRenderFile('_ajax-home.php', array('id' => $page->id))?> </div> I did a clean install of 3.0.255 and it runs ok. Replaced site files and imported my d.b as usual but gives red error screen - The error I'm getting : Hmm… Fatal Error: Uncaught Error: Call to undefined function wireRenderFile() in site/templates/home.php:3 #0 wire/core/TemplateFile.php (328): require() #1 wire/core/Wire.php (413): TemplateFile->___render() #2 wire/core/WireHooks.php (1018): Wire->_callMethod('___render', Array) #3 wire/core/Wire.php (484): WireHooks->runHooks(Object(TemplateFile), 'render', Array) #4 wire/modules/PageRender.module (547): Wire->__call('render', Array) #5 wire/core/Page.php (3152): PageRender->render(Object(HomePage), Array) #6 wire/core/Wire.php (416): Page->___renderPage(Array) #7 wire/core/WireHooks.php (1018): Wire->_callMethod('___renderPage', Array) #8 wire/core/Wire.php (484): WireHooks->runHooks(Object(HomePage), 'renderPage', Array) #9 wire/core/Page.php (3097): Wire->__call('renderPage', Array) #10 wire/core/Wire.php (413): Page->___render() #11 wire/core/WireHooks.php (1018): Wire->_callMethod('___render', Array) #12 wire/core/Wire.php (484): WireHooks->runHooks(Object(HomePage), 'render', Array) #13 wire/modules/Process/ProcessPageView.module (193): Wire->__call('render', Array) #14 wire/modules/Process/ProcessPageView.module (114): ProcessPageView->renderPage(Object(HomePage), Object(PagesRequest)) #15 wire/core/Wire.php (416): ProcessPageView->___execute(true) #16 wire/core/WireHooks.php (1018): Wire->_callMethod('___execute', Array) #17 wire/core/Wire.php (484): WireHooks->runHooks(Object(ProcessPageView), 'execute', Array) #18 index.php (55): Wire->__call('execute', Array) #19 {main} thrown (line 3 of site/templates/home.php) I can login to admin on back end ok. Any attempt to view a page from admin gives the error above as they all use basic-page template with the same markup region. Like I said it all works fine on my localhost on 3.0.229 Any ideas? Thanks .
-
I hope no-one minds, but I have removed dai() and bdai() in favour of adding a "copy as plain text for AI agent" button for the regular d() and bd() calls. which will put the following in your clipboard. Call: d($page); Location: site/assets/cache/TracyDebugger/consoleCode_1778105690621_f3xspie7xg.php:2 ProcessWire\Page #246 instanceID: 10 id: 3 name: 'page' path: '/admin/page/' status: 'locked, system' template: 'admin' parent: '/admin/' numChildren: 10 sort: 0 sortfield: 'sort' created: '2025-12-06 18:34:42 (5 months ago)' modified: '2025-12-06 18:34:42 (5 months ago)' published: '2025-12-07 12:34:42 (5 months ago)' createdUser: 'ajones' modifiedUser: 'ajones' isLoaded: 1 outputFormatting: 1 hooks: array (17) | '->render' => array (2) | | 0 => 'TracyDebugger->logRequests() in TracyDebugger.module.php' | | 1 => 'anonymous function() in TracyDebugger.module.php (100.1)' | 'Page::getRequestData' => 'TracyDebugger->getRequestData() in TracyDebugger.module.php' | 'Page::logRequests' => 'TracyDebugger->logRequests() in TracyDebugger.module.php' | 'Page::editable' => 'PagePermissions->editable() in PagePermissions.module' | 'Page::publishable' => 'PagePermissions->publishable() in PagePermissions.module' | 'Page::viewable' => 'PagePermissions->viewable() in PagePermissions.module' | 'Page::listable' => 'PagePermissions->listable() in PagePermissions.module' | 'Page::deleteable' => 'PagePermissions->deleteable() in PagePermissions.module' | 'Page::deletable' => 'PagePermissions->deleteable() in PagePermissions.module' | 'Page::trashable' => 'PagePermissions->trashable() in PagePermissions.module' | 'Page::restorable' => 'PagePermissions->restorable() in PagePermissions.module' | 'Page::addable' => 'PagePermissions->addable() in PagePermissions.module' | 'Page::moveable' => 'PagePermissions->moveable() in PagePermissions.module' | 'Page::sortable' => 'PagePermissions->sortable() in PagePermissions.module' | 'Page::cloneable' => 'PagePermissions->cloneable() in PagePermissions.module' | 'after Page::render' => 'anonymous function() in FrontendMatrixEdit.module.php' | 'before Wire::trackException' => 'anonymous function() in TracyDebugger.module.php' data: array (2) | 'title' => 'Pages' | 'process' => 'ProcessPageList' I feel like this is the best of both worlds.
-
Hello @ all I want to share a new module with you, which makes the creation and validation of forms easy. Take a look at the following example of a simple contact form: // A very simple example of a contactform for demonstration purposes $form = new Form('contactform'); $gender = new Select('gender'); $gender->setLabel('Gender'); $gender->addOption('Mister', '0'); $gender->addOption('Miss', '1'); $form->add($gender); $surname = new InputText('surname'); $surname->setLabel('Surname'); $surname->setRule('required'); $form->add($surname); $name = new InputText('name'); $name->setLabel('Name'); $name->setRule('required'); $form->add($name); $email = new InputText('email'); $email->setLabel('E-Mail'); $email->setRule('required'); $form->add($email); $subject = new InputText('subject'); $subject->setLabel('Subject'); $subject->setRule('required'); $form->add($subject); $message = new Textarea('message'); $message->setLabel('Message'); $message->setRule('required'); $form->add($message); $privacy = new InputCheckbox('privacy'); $privacy->setLabel('I accept the privacy policy'); $privacy->setRule('required')->setCustomMessage('You have to accept our privacy policy'); $form->add($privacy); $button = new Button('submit'); $button->setAttribute('value', 'Send'); $form->add($button); if($form->isValid()){ print_r($form->getValues()); // do what you want } // render the form echo $form->render(); This piece of code creates a simple contact form and validates it according to the validation rules set. Inside the isValid() method you can run your code (fe sending an email) Highlights: 30+ validation types Support for UiKit 3 and Bootstrap 5 CSS framework SPAM protection Highly customizable Hookable methods for further customization Multi-language You can download and find really extensive information on how to use at https://github.com/juergenweb/FrontendForms. Please report errors or suggestions directly in GitHub. Best regards and happy testing ? If you have downloaded the module in the past I recommend you to uninstall the module completely and install the newest version 2.1.14. There are a lot of changes in the new version, so please test carefully.
- 453 replies
-
- 26
-
-
-
module StripePaymentLinks – Simple Checkout Integration for ProcessWire
Mikel replied to Mikel's topic in Modules/Plugins
StripePaymentLinks 1.2.0 — Electronic withdrawal for B2C distance contracts Hi all, just shipped new version of StripePaymentLinks, which adds a complete electronic right-of-withdrawal flow for online merchants. Why now EU Directive 2023/2673 (amending the Consumer Rights Directive 2011/83/EU) requires every online trader selling to EU consumers — goods, services, or digital products — to provide an easily accessible "withdrawal button" by 19 June 2026. Member states had to transpose the directive into national law by 19 December 2025; Austria's FAGG amendment is one of the implementations now landing. The guiding principle: withdrawing from a contract must not be more burdensome than concluding it. This module ships an electronic withdrawal flow that satisfies that principle, with all legal wording editable per site so it works under any national transposition. What's new Withdrawal modal on every frontend page — Bootstrap 5 modal with form → confirmation → success steps. Renders only when invoked (link or ?withdraw=1 URL parameter); zero overhead otherwise. Per-user withdrawal log — every submission is stored in a repeater on the user template (spl_withdrawals) with timestamp, products affected, salted HMAC-SHA256 IP hash, and a status field that the merchant can update from the backend. Two-mail flow — the customer gets a confirmation of the withdrawal, the merchant gets an internal notification with a direct link to the user profile. Both go through the existing universal mail layout. Order-confirmation mail now includes a consumer-rights block — auto-injected for every purchase, with two outcomes depending on the product: Right of withdrawal applies → withdrawal instructions are rendered. Right has been waived (e.g. immediate digital delivery with explicit consent) → waiver acknowledgement is rendered. Site-editable legal text — two TinyMCE config fields (mailWithdrawalText, mailWaiverText) let each site operator write the exact legal wording their jurisdiction requires. Placeholder system including {products}, {provider}, {contact_email}, {order_id}, {order_date}, {name}, {email}, {today} and — for the TinyMCE editor — anchor-pair placeholders that survive the editor's href-stripping: {withdrawal_mail}TEXT{withdrawal_mail_end} → expands to a prefilled mailto: link (subject + body from translatable defaults) {withdrawal_online}TEXT{withdrawal_online_end} → expands to a ?withdraw=1 deeplink that auto-opens the modal Built-in protection — honeypot field, per-IP rate-limit, server-side CSRF, deliverability headers (Auto-Submitted, X-Auto-Response-Suppress, proper Reply-To). Setup is a few field saves ... In the module config there's a new Withdrawal fieldset with five fields: Internal notification email (falls back to $config->adminEmail) Contact email shown in the form (falls back to sender email) Privacy policy page (page-select) Withdrawal text — right of withdrawal applies (TinyMCE) Waiver text — right of withdrawal does not apply (TinyMCE) The module creates all required fields, the spl_withdrawals repeater and status options on first save / module upgrade — fully idempotent. ... and adding the trigger link to your templates The withdrawal modal is auto-injected on every frontend page — you only need to render a link to open it. The module ships a helper for that: echo $modules->get('StripePaymentLinks')->renderWithdrawalLink(); That gives you the legally required, always-visible withdrawal entry point. Typical place: site footer or account menu. The helper takes two optional arguments — a CSS class and a custom label — so it adapts to whatever markup your theme uses: echo $modules->get('StripePaymentLinks')->renderWithdrawalLink('nav-link fw-bold'); echo $modules->get('StripePaymentLinks')->renderWithdrawalLink('', 'WITHDRAW'); Anything that links to your site root with ?withdraw=1 also auto-opens the modal — useful for putting a direct withdrawal link inside order-confirmation mails, transactional notifications, or PDF receipts. That's the whole frontend integration — one helper call, no JavaScript wiring, no modal HTML in your templates. Translatable All UI strings (modal labels, mail subjects/bodies, status names) are PW-translatable. Happy to hear feedback, edge cases, or implementation experiences from other EU jurisdictions. Cheers, Mike -
How to transfer a Processwire site from one server to another
Najib replied to modifiedcontent's topic in General Support
Honestly, you’ve got everything you need with just the files and a database dump. I’ve done this exact migration a few times, and ProcessWire is actually one of the easiest CMSs to move because it doesn’t hardcode paths in the DB. Just upload your files to the Ubuntu server, import the SQL via phpMyAdmin or CLI, and update your /site/config.php with the new database credentials. The only real "gotcha" is usually file permissions or missing .htaccess—make sure mod_rewrite is enabled on the new Ubuntu box and double-check that your /assets/ folders are writable by the server, otherwise your images won't render. You definitely don't need a fresh install first; just "drop and swap" works fine. -
Hi guys, I've been working on a full EditorJS integration for ProcessWire and it's reached a point where I'd like to share it and get some testing feedback before calling it stable. GitHub: https://github.com/mxmsmnv/FieldtypeRapid This is a development preview. The core functionality works, but I'm looking for testers on different server configurations and ProcessWire setups before a stable release. Please report issues on GitHub. Why EditorJS instead of CKEditor or TinyMCE? CKEditor and TinyMCE are document editors — they produce a single blob of HTML. That HTML is hard to restructure, style consistently, or repurpose for different output targets (web, mobile, PDF, API). EditorJS is a block-based editor. Every paragraph, heading, image, and quote is a separate JSON object with a type and structured data. This means: Content is stored as clean JSON, not tangled HTML Each block type can be rendered differently per context Easy to add, reorder, or remove blocks without breaking surrounding content Output is fully controlled server-side via PHP renderers — no frontend JS required It's closer in concept to Notion or Gutenberg than to a traditional WYSIWYG. What Rapid does: 17 block types: paragraph, header (h1–h6 with auto anchor IDs), quote, nested lists, table, code, delimiter, warning, checklist, raw HTML, image (WebP convert + resize), file attachments, YouTube/Vimeo embed, alert (8 color variants), toggle/accordion, link preview with OG metadata Inline tools: bold, italic, underline, inline code, marker, link Template API: echo $page->body; // render all blocks echo $page->body->toText(); // plain text for meta/search echo $page->body->renderWith($renderer); // custom renderer 4 CSS frameworks — Vanilla, Tailwind, Bootstrap 5, UIkit 3 Frontend editing — inline editor on frontend for authorized users No build step needed — pre-built js/dist/editor.js included. Requirements: ProcessWire 3.0.200+, PHP 8.2+ Any feedback on installation, rendering edge cases, or block behaviour is welcome!
- 8 replies
-
- 17
-
-
-
Good day! I need to render a page output for a given url segment from API. There is a way to render a page with $page->render() and now with $page->renderPage(). But I can't find a way to render a page passing in an url segment. So the output would be not of site.com/page but of site.com/page/urlsegment to make it more clear. Please share your ideas.
-
So I tried the following but I'm not sure if it's good practice or not. Sharing in case anyone finds it useful. This setup requires that the PageImageSource module is installed with srcset image sizes setup in its config. It basically involves creating a page where cross-site images can be uploaded - like a simple image library. I created a short function to make it easier to grab the images where needed. Adding ->render() will output <picture> with srcsets. The media page should be set as hidden. Create a template, give it a name and add a multi-image field In the templates folder create _func.php and add the following: <?php namespace ProcessWire; /* Create a fn. that returns a Pageimage object from media library page you just created. Here i called my function medialib, and $medialibrary is the name of my template.*/ function medialib($filename) { static $medialibrary = null; if (is_null($medialibrary)) { // REPLACE the get directory with the Media Library page url created in admin. Mine was /media-library/ $medialibrary = wire('pages')->get('/media-library/'); } // Returns the image object if found, or null return $medialibrary->id ? $medialibrary->images->get("name=$filename") : null; } Add the include for _func.php in your _init.php in the templates folder: <?php namespace ProcessWire; // This makes the medialib() function available to all templates include_once("./_func.php"); // Render media library images on other pages using format: /*<?= medialib("imagefile.jpg")?->render() ?>*/ Function can now be called in template markup using: // Render media library images on other pages using format: <?= medialib("imagefile.jpg")?->render() ?> //Example <?=medialib("imageNameMustMatchFileName.jpg")->render()?> // Outputs <picture> tag. For CSS styling, target outer div class like so: .class img{.....} Combining this with another module might https://processwire.com/modules/process-media-lister/ might be useful.
- 1 reply
-
- images
- image library
-
(and 3 more)
Tagged with:
-
Hey all! This is a module to enhance forms built using the Pro FormBuilder module by providing the ability to submit them in place using AJAX and HTMX. FormBuilderHtmx works in harmony with FormBuilder by handling front-end rendering and AJAX and lets FormBuilder manage form configuration and processing. FormBuilderHtmx provides a drop-in replacement for the $forms->render() method and provides all native features and behavior (and adds a few extra superpowers to boot). Noteworthy features: Zero configuration, install and render AJAX powered FormBuilder forms immediately Render multiple forms on the same page. Supports both multiple instances of the same form or different forms. Each form is processed independently. Non-intrusive, can be used alongside FormBuilder's native rendering methods and does not modify core module behavior Perfect for forms embedded in popups and modals Does not conflict with styling and other JavaScript already in-place, only handles the form submission/response loop Automatically disables the `Submit` button on submission to prevent duplicate requests Provides the ability to add a custom 'spinner' shown when a form is being processed Gives you the ability to add additional HTML attributes to your FormBuilder <form> element. Add additional custom functionality using HTMX attributes, hook into form actions with your JavaScript, or even add AlpineJS directly to your forms. Compatible with FieldtypeFormSelect, let users choose which forms to embed, your code determines how they are rendered Uses HTMX, a stable, powerful, and tiny (14kb gzipped) library, installation documentation available here This module is BYOH (Bring Your Own HTMX) in that the HTMX library is not included or available within this module. This ensures long-term stability by not locking FormBuilderHtmx to external asset versioning. FormBuilderHtmx uses stable core HTMX features so use the library version that works for you and confidently add this module to both new, existing, and future ProcessWire applications. In some instances CSRF protection may need to be disabled to submit forms with this module. Just test your forms and you're good to go. Using this module is truly easy. <!-- Replace the native $forms->render() method with $htmxForms->render() --> <?php $htmxForm = $htmxForms->render('your_form_name') ?> <!-- Use native ProcessWire properties and methods as usual --> <?php echo $htmxForm->styles; echo $htmxForm->scripts; echo $htmxForm; ?> Presto. You can optionally include a helpful 'spinner' or activity animation that will be showed to users while their form request is being processed. Check out these ready-to-go examples you can use in your projects. <style> /* Add these styles to your CSS, the `.htmx-request` must be present as shown here. Be sure to include any CSS your 'spinner' may need, and style everything as desired */ .activity-indicator { display: none; } .htmx-request .activity-indicator, .htmx-request.activity-indicator { display: block; } </style> <!-- Optional second argument matches that of the $forms->render() method for pre-populated values The third argument is the CSS selector matching your 'spinner' element --> <?= $htmxForms->render('your_form_name', [], '#indicator-for-the-form') ?> <div id="indicator-for-the-form" class="activity-indicator"> <span class="spinner"></span> </div> Presto (again) Check out the documentation for detailed usage and other available features. Pull requests and issues filed on Github are welcome, or drop by here to get some help! Install as a ProcessWire module Install using Composer Download from the FormBuilderHtmx Github repository . Cheers!
- 32 replies
-
- 26
-
-
-
- formbuilder
- ajax
-
(and 1 more)
Tagged with:
-
New blog: All about custom page classes in ProcessWire
ryan replied to ryan's topic in News & Announcements
@Ivan Gretsky I'm always a little reluctant to make a blanket statement like "avoid markup in page classes", but I'm referring to what I think works best with the projects I work on. The files in /site/templates/ are the view layer, as nearly all code in there is aimed at generating markup/output. Even if something isn't directly generating markup, it's still finding and preparing things for output. Most markup comes from "partials", which are files that I put in /site/templates/parts/, or if exclusive to a particular template, then /site/templates/[template]/. And then I either include() them, or files()->render() them from the site's template files. I primarily use Markup Regions. The _main.php establishes the base markup: <?php namespace ProcessWire; /** @var Page $page **/ ?><!DOCTYPE html> <html> <head id="html-head"> <?php include('./parts/html-head.php');?> </head> <body id="html-body"> <header id="header"> <?php include('./parts/header.php');?> </header> <h1 id="headline"><?=$page->title?></h1> <main id="content"> <?=$page->body?> </main> <aside id="sidebar" pw-optional> </aside> <footer id="footer"> <?php include('./parts/footer.php');?> </footer> </body> </html> Below is a template file for the /products/ page which lists product pages, supports pagination, and uses URL segments for sorting: <?php namespace ProcessWire; // products.php /** @var ProductsPage|CategoryPage $page */ $products = findProducts($page); $body = input()->pageNum === 1 ? $page->body : ''; $headline = $page->get('headline|title'); ?> <h1 id="headline"><?=$headline?></h1> <main id="content"> <?=$body?> <?php include('./parts/sorts.php'); // no expects ?> <?php include('./parts/products-list.php'); // expects $products ?> </main> <aside id="sidebar"> <?php include('./parts/categories-list.php'); // no expects ?> </aside> The category template file works exactly the same way, except that it lists products for the category rather than listing all products. The same code works either way, so "category.php" just includes "products.php": <?php namespace ProcessWire; // category.php include('./products.php'); There's that findProducts() function above in the products.php template file -- I usually have helper functions in a /site/templates/_func.php, /site/templates/_products.php, or /site/templates/products/func.php (assuming exclusive for "products"). Another place would be for the ProductsPage and CategoryPage to have findProducts() methods, but usually I don't want the page classes getting involved with detecting stuff about the current request (sort, pageNum, etc.) so like these in a simple function library file: <?php namespace ProcessWire; // file site/templates/_func.php included by _init.php function getSorts(): array { return [ 'name' => 'A-Z', '-name' => 'Z-A', 'price' => 'Price (low-high)', '-price' => 'Price (high-low)', 'created' => 'Date added (oldest)', '-created' => 'Date added (newest)' ]; } function getSort(): string { $sorts = getSorts(); $sort = input()->urlSegment('sort-(*)'); if(empty($sort)) $sort = 'name'; if(!isset($sorts[$sort])) wire404('Invalid sort'); return $sort; } function findProducts($page, $limit = 20): PageArray { $sort = getSort(); $find = "template=product, sort=$sort, limit=$limit"; if($page instanceof CategoryPage) $find .= ", categories=$page"; return pages()->find($find); } Here's an example of a ./parts/products-list.php file: <?php namespace ProcessWire; // file: parts/products-list.php /** @var PageArray|ProductPage[] $products */ $subhead = $products->getPaginationStr('Products'); $pagination = files()->render('parts/pagination.php', [ 'items' => $products ]); ?> <h3><?=$subhead?></h3> <?=$pagination?> <ul class="products-list"> <?php foreach($products as $product): ? <?php include('./parts/products-item.php'); // expects $product ?> <?php endforeach; ?> </ul> And the parts/products-item.php, though in reality there would likely be more to it: <?php namespace ProcessWire; // file: parts/products-item.php /** @var ProductPage $product */ ?> <li class="products-item"> <h3><?=$product->title?></h3> <p><?=$product->summary?></p> <p><a href="<?=$product->url?>">View Details</a></p> </li> To complete it, here's the parts/sorts.php file: <?php namespace ProcessWire; // file: parts/sorts.php $currentSort = getSort(); $url = page()->url; $sorts = []; foreach(getSorts() as $sort => $label) { if($sort != $currentSort) $label = "<a href='{$url}sort-$sort/'>$label</a>"; $sorts[] = $label; } echo "<p class='sorts'>Sort by: " . implode(' / ', $sorts) . "</p>"; If I start needing to output products in more places in the site, then I'll usually do fewer include()'s and move the rendering to dedicated functions. That way these things render in their own variable namespace and don't bleed variables or overwrite variables in the main rendering. So this would also go in that _func.php (or _products.php or ./products/func.php) mentioned above, and the include() calls in template fiels would be replaced with render...() calls: function renderProducts(PageArray $products): string { return files()->render('parts/products-list.php', [ 'products' => $products ]); } function renderCategories(): string { return files()->render('parts/categories-list.php'); } function renderPagination(PageArray $items) { return files()->render('parts/pagination.php', [ 'items' => $items ]); } So if using render() functions then the <main> with the include('./parts/products-list.php'); would get replaced with this: <main id="content"> <?=$body?> <?=renderProducts($products)?> </main> Ah yes, I hadn't thought about that in a long time. I can't remember if that was implemented yet or not. I'll find out. If not yet implemented I'll have to implement it, it should be fairly simple. -
PW: 3.0.255 PHP: 8.3.3 Sadly I now have the same issue and I can't find a way around it. Duplicate sites, create a new page on one then try to export the page to the duplicate site and get this error: Error: Call to a member function getLanguageValue() on null in wire/modules/LanguageSupport/FieldtypeTextareaLanguage.module:126 #0 [internal function]: FieldtypeTextareaLanguage->___importValue() #1 wire/core/Wire.php (425): call_user_func_array() #2 wire/core/WireHooks.php (1018): Wire->_callMethod() #3 wire/core/Wire.php (484): WireHooks->runHooks() #4 wire/core/PagesExportImport.php (998): Wire->__call() #5 wire/core/PagesExportImport.php (685): PagesExportImport->importFieldValue() #6 wire/core/PagesExportImport.php (535): PagesExportImport->arrayToPage() #7 wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeaterPorter.php (255): PagesExportImport->arrayToPages() #8 wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module (1006): FieldtypeRepeaterPorter->importValue() #9 [internal function]: FieldtypeRepeater->___importValue() #10 wire/core/Wire.php (425): call_user_func_array() #11 wire/core/WireHooks.php (1018): Wire->_callMethod() #12 wire/core/Wire.php (484): WireHooks->runHooks() #13 wire/core/PagesExportImport.php (998): Wire->__call() #14 wire/core/PagesExportImport.php (685): PagesExportImport->importFieldValue() #15 wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module (619): PagesExportImport->arrayToPage() #16 wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module (320): ProcessPagesExportImport->processImportItemToPage() #17 wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module (242): ProcessPagesExportImport->processImportSubmit() #18 wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module (76): ProcessPagesExportImport->processImport() #19 wire/core/Wire.php (413): ProcessPagesExportImport->___execute() #20 wire/core/WireHooks.php (1018): Wire->_callMethod() #21 wire/core/Wire.php (484): WireHooks->runHooks() #22 wire/core/ProcessController.php (362): Wire->__call() #23 wire/core/Wire.php (413): ProcessController->___execute() #24 wire/core/WireHooks.php (1018): Wire->_callMethod() #25 wire/core/Wire.php (484): WireHooks->runHooks() #26 wire/core/admin.php (174): Wire->__call() #27 wire/modules/AdminTheme/AdminThemeUikit/controller.php (15): require('/home/u99035861...') #28 site/templates/admin.php (15): require('/home/u99035861...') #29 wire/core/TemplateFile.php (328): require('/home/u99035861...') #30 wire/core/Wire.php (413): TemplateFile->___render() #31 wire/core/WireHooks.php (1018): Wire->_callMethod() #32 wire/core/Wire.php (484): WireHooks->runHooks() #33 wire/modules/PageRender.module (547): Wire->__call() #34 wire/core/Page.php (3152): PageRender->render() #35 wire/core/Wire.php (416): Page->___renderPage() #36 wire/core/WireHooks.php (1018): Wire->_callMethod() #37 wire/core/Wire.php (484): WireHooks->runHooks() #38 wire/core/Page.php (3097): Wire->__call() #39 wire/core/Wire.php (413): Page->___render() #40 wire/core/WireHooks.php (1018): Wire->_callMethod() #41 wire/core/Wire.php (484): WireHooks->runHooks() #42 wire/modules/Process/ProcessPageView.module (193): Wire->__call() #43 wire/modules/Process/ProcessPageView.module (114): ProcessPageView->renderPage() #44 wire/core/Wire.php (416): ProcessPageView->___execute() #45 wire/core/WireHooks.php (1018): Wire->_callMethod() #46 wire/core/Wire.php (484): WireHooks->runHooks() #47 index.php (56): Wire->__call() #48 {main}
-
I made a little update to 1.3.7, bringing it to the current PHP8+ and PW3+ standards. Changes in 1.3.7 (2026-04-22) Updated for ProcessWire 3.x and PHP 8.x compatibility Declared class properties explicitly to fix PHP 8.2 dynamic property deprecation Fixed nullable type hints on render() signature (?array, ?Page) required since PHP 8.0 Replaced global wire() calls with $this->wire() for correct PW3 multi-instance scoping Replaced new PageArray() with $this->wire(new PageArray()) for proper instance binding Replaced object identity comparisons (===) with ID-based comparisons (->id ===) for $is_current and $is_root Replaced loose null comparisons (!= null) with strict (!== null) throughout Fixed misleading single-line if/else in xtemplates block with proper braces Removed unused $v_unformatted variable in parsePlaceholders() Replaced $this->fields / $this->users magic access with $this->wire('fields') / $this->wire('users') Updated getModuleInfo(): added icon, requires (PHP>=8.0, ProcessWire>=3.0.0), updated href to https Bumped version from 1.3.6 to 1.3.7 I only tested it on a site I'm currently working on, and it still works the same. But if you encounter any issues please let me know.
-
PW 3.0.255 ProcessDatabaseBackups 0.0.6 PHP 8.3.30 On restore I am seeing this error: Passing null to parameter #1 ($num) of type float is deprecated in ***/site/modules/ProcessDatabaseBackups/ProcessDatabaseBackups.module on line 243 The line in question seems to be this one: $this->tdSort($file['numInserts'], number_format($file['numInserts'])), Within: foreach($backupFiles as $id => $file) { $numFiles++; $numTables = $file['numTables']; if($numTables && !count($file['tables'])) $numTables .= " " . $this->_('(all)'); $basename = $file['basename']; $time = $file['time'] ? $file['time'] : $file['mtime']; if($file['description']) $basename .= '*'; $actions = array(); foreach($this->getFileActions($file) as $action) { $actions[] = $this->aTooltip($action['href'], wireIconMarkup($action['icon'], 'fw'), $action['label']); } $checkbox->attr('id', "delete_" . $sanitizer->fieldName($id)); $checkbox->attr('value', $id); $table->row(array( $this->nowrap($basename) => "./info/?id=$id", $this->tdSort(strtotime($time), wireRelativeTimeStr($time, true)), $this->nowrap($numTables), $this->tdSort($file['numInserts'], number_format($file['numInserts'])), $this->tdSort($file['size'], wireBytesStr($file['size'])), $this->nowrap(implode(' ', $actions)), $checkbox->render(), )); } If anyone has an ideas for a fix please do let me know as I use this module a lot!
-
Hi, everyone! While working on a client project we were looking for a way to let editors apply CSS classes to individual images in rich text fields — quickly, visually, and also in the frontend editor. ProcessWire already has several ways to get CSS classes onto images, so it's worth being precise about what this module does differently: TextformatterFluidImages adds one class to all images automatically — great for img-fluid across the board, but there's no per-image choice. TextformatterImageInterceptor is more powerful: editors tag images in the image field, and the Textformatter applies the corresponding classes at render time. The logic is developer-defined and centralized, which is exactly right when you want consistent, rule-based image treatment. But the class is invisible in the editor, applied only in the frontend output, and editors have to set the tag in a completely separate place from where they're actually working. TinyMCE's built-in styleFormatsCSS is the closest thing to what we wanted. You write CSS, ProcessWire turns it into a Styles dropdown. It works, but the dropdown is generic — it shows all defined styles regardless of what's selected — and there's a known accumulation issue where nothing prevents float-left float-right ending up on the same image. And it doesn't work in the frontend editor. What we needed was simpler: editor clicks an image, picks a style, sees immediately which styles are active, can combine them or remove them individually. No dialogs, no separate fields, no render-time magic — the class goes directly into the <img> tag in the saved HTML, visible and editable right there in the editor. That's what this module does: It registers a context toolbar in TinyMCE that appears as a floating "Image Style" button when an image is selected. For CKEditor the same options show up in the right-click context menu. The class list is defined once in the module settings and works across both editors — no separate configuration per editor type. Each entry shows a checkmark when active, clicking it again removes it, multiple classes can be combined freely. Works in the admin and in the frontend editor. Complete Readme on GitHub: https://github.com/frameless-at/ProcessImageClasses and the module directory. Any thoughts on further improvements welcome! Cheers, Mike
- 1 reply
-
- 12
-
-
I have it in a Controller file in renderSomething() method called from render(). Function or throw - doesn't matter. I moved this code to init() and even to wireframe.php. The issue is the same. The server returns a 404 code but doesn't redirect and shows an error page. I have tried this on two other sites with Wireframe. And the behaviour is different there. If i put the code in wireframe.php there, it gives me a 500 error. But in controller it works ok and shows the 404 page. Those 2 sites are not multilingual and not so heavily modified... Can you suggest a way I can debug further? I have found the issue. The template of 404 page was changed to a template that caused an infinite loop. Sorry for bothering you @teppo. But natural intellect also needs prompts and you helped me with a one)
-
AgeWire — Age Verification Module for ProcessWire
maximus replied to maximus's topic in Modules/Plugins
AgeWire has been updated — here's what's new since the original release. Multi-framework support The module now supports four CSS frameworks. You pick whichever one your project already uses: Vanilla CSS — fully self-contained, no dependencies, no CDN required Tailwind CSS — the original experience, 13 themes and 4 animations (unchanged) Bootstrap 5 — renders using native Bootstrap card, btn, form-control and alert classes UIkit 3 — renders using native UIkit card, button and grid components Each framework has an independent CDN toggle. If you already load Bootstrap or UIkit in your templates, just uncheck it and the module won't load anything extra. Security fixes CSRF token is now validated on every verification POST Fixed a bug where ProcessWire would render the full page HTML after the JSON response, causing JSON.parse errors in the browser. The AJAX handler now calls ob_clean() before sending JSON to discard any buffered output Strict date parsing with DateTime::createFromFormat — relative strings like "tomorrow" or "-18 years" are rejected Redirect URL validated to http://, https://, or site-relative paths only Custom CSS field sanitized against </style> injection JS console.log output is now gated behind $config->debug — no noise in production Admin UI The config page is now a two-column layout. General Settings and Framework & Theme are open side by side on first load. The rest (Modal Content, Date Picker, Agreement, Exclusions) are collapsed into logical groups — much less scrolling. PHP 8.2+ The module now requires PHP 8.2 and uses typed return types, match expressions, str_contains, str_starts_with, and unnamed catches throughout. GitHub: https://github.com/mxmsmnv/AgeWire Feedback and PRs welcome as always!- 1 reply
-
- 2
-
-
I just reinstall Processwire with latest RockPageBuilder (6.5.2). After adding a Content-Element (Afred oder Backend) I get an error: Error: Call to a member function render() on string search► RockDevTools LiveReload is active File: .../modules/RockPageBuilder/RockPageBuilder.module.php:1596 1586: } 1587: 1588: /** 1589: * Render content of blocks field 1590: */ 1591: public function render($renderPlus = true) 1592: { 1593: $page = $this->wire->page; 1594: $field = $page->getFormatted(self::field_blocks); 1595: if (!$field) return; 1596: $html = $field->render($renderPlus); 1597: $rf = $this->wire->rockfrontend; 1598: if ($rf) return $rf->html($html); 1599: return $html; 1600: } In Latte-Template: {if $modules->isInstalled('RockPageBuilder')} <main sortable> {$rockpagebuilder->render(true)} </main> {else} If I delete the Content-Element it works like expected.