Search the Community
Showing results for 'files'.
-
There has been a lot of ProcessWire work covered this week! Here's a summary: 1. AgentTools module has been upgraded with "Site Engineer", an AI agent now built into your admin, and you can ask it questions, create migrations, or have it make other web development updates to your site by going to Setup > Agent Tools > Engineer. To enable the Engineer, you need an Anthropic API key, an OpenAI API key, or an OpenAI compatible API key (apparently several others use the OpenAI key standard). You can optionally put Engineer in "read-only" mode, which is what I do for production sites. In read-only mode, it answers questions and provides you with code for making updates yourself. But if read-only mode is not enabled, then it can act as your web developer and make changes directly, which is what I use with development sites. AgentTools provides full context to Engineer on your site's pages, fields and templates. If using ProcessWire 3.0.258 (or newer) it also provides the new API.md files to help AI know how to best work with all of ProcessWire's Fieldtypes. If you are having Engineer create or manipulate Fields on your site, it's a good idea to have 3.0.258 for the API.md support. Engineer also supports prompt caching for up to 1 hour in order to limit token usage. 2. The AgentTools now has JSON site-map generation features for AI agents. This enables an AI agent to see the full scope of your site. A second site-map feature focuses on all your site's templates and fields, essentially providing the full site schema to the AI agent. 3. In the core, we've added API.md files for all 18 of ProcessWire's core Fieldtypes, except for the comments and cache Fieldtypes, so far. In order to facilitiate this, and to facilitiate AI agent accessibility, all of ProcessWire's Fieldtypes now have their own directories as well. 4. After looking at all the API.md files, it became clear that there was plenty of room for improvement in the APIs of several Fieldtypes, so there have been major core updates to several Fieldtypes, as well as the Fields, Templates and Fieldgroups classes. 5. A Fieldtype testing framework has been built, which tests the full scope of 20 ProcessWire Fieldtypes (all the core ones, plus FieldtypeRepeaterMatrix and FieldtypeTable). It tests field creation, manipulation, traversal (where applicable), sorting (where applicable), searching with selectors, and more. I'll be uploading the testing framework to GitHub soon as well. 6. The new testing framework identified some bugs, which have been fixed. Most notable were selector matching bugs in FieldtypeFloat and FieldtypeDatetime. 7. There has been some refactoring in ProFields FieldtypeRepeaterMatrix and FieldtypeTable, plus API.md files have been generated for both. New versions should be ready soon. In fact, that applies to all of the ProFields, and I hope to cover FieldtypeCombo and FieldtypeCustom next week. 8. ProcessWire 3.0.258 has a whole lot of improvements, changes and fixes in it. Here's the commit log: https://github.com/processwire/processwire/commits/dev/ 9. Back to working on PagesVersionsPro (and have been for a few weeks) but more on that later. 10. There's probably more, but that's all I can remember at the moment. 🙂 Thanks for reading and have a great weekend! Basic examples of using Engineer for migrations:
- 25 replies
-
- 20
-
-
-
I wanted a way to chat with my Processwire site and built a Module (PW MCP) and an MCP server to connect into it. It's a private repo at the moment but it can be public if anyone finds it useful. It's basically a way to use the Cursor Chat Ui to query my site, fields, templates and content. Here's part of the readme which explains it better. What Is It? ProcessWire MCP is a bridge between ProcessWire and Cursor IDE (the AI-powered code editor). It lets you query your ProcessWire site's structure and content directly from Cursor's chat interface using natural language. Instead of writing selectors or browsing the admin, you can just ask: "What templates does this site have?" "Show me the fields on the blog-post template" "Search for pages containing 'summer'" "Find all images with 'lake' in the filename" Why I Built It Cursor can see your template files and code in the local directory, but it can't see what's actually in your ProcessWire database — which templates and fields are registered, what pages exist, or what content they contain. With ProcessWire MCP, the AI can: Query the actual database schema (not just parse template files) Look up page content by ID, path, or selector Understand field configurations (types, settings, which templates use them) Search across all text content and find files/images Get RepeaterMatrix content with type labels See file metadata (dimensions, descriptions, URLs) It's the difference between seeing $page->body in code vs. knowing what that page's body actually contains. Architecture Cursor Chat → MCP Server (Node.js) → PHP CLI → ProcessWire API The module consists of: PwMcp — A ProcessWire module with a CLI interface mcp-server — A Node.js server that speaks the Model Context Protocol The CLI can also be used standalone for quick queries from terminal. Available Commands Command Description health Check connection and get site info list-templates List all templates with field counts get-template [name] Get template details and fields list-fields List all fields with types get-field [name] Get field details and usage get-page [id\|path] Get page by ID or path with all field values query-pages [selector] Query pages using PW selectors search [query] Search content across all text fields search-files [query] Search files by name/extension export-schema Export complete site schema Example: Health Check php site/modules/PwMcp/bin/pw-mcp.php health --pretty { "status": "ok", "pwVersion": "3.0.241", "siteName": "www.example.com", "moduleLoaded": true, "counts": { "templates": 45, "fields": 72, "pages": 960 } } Example: Content Search Ask Cursor: "Search for pages containing 'summer'" { "query": "summer", "count": 5, "results": [ { "id": 1764, "title": "Lake District walks in summer", "path": "/guides/lake-district-summer/", "template": "page-guide", "matchedField": "Body", "snippet": "The Lake District offers some of the best walking trails in summer. From gentle lakeside strolls to challenging fell walks..." } ] } Example: File Search Ask Cursor: "Find images with 'lake' in the filename" { "query": "lake", "count": 5, "results": [ { "filename": "lake-windermere-sunset.jpg", "url": "/site/assets/files/1070/lake-windermere-sunset.jpg", "size": 31207, "sizeStr": "30.5 kB", "description": "Sunset over Lake Windermere", "field": "Images", "page": { "id": 1070, "title": "Lake District walks in summer", "path": "/guides/lake-district-summer/" }, "width": 500, "height": 626 } ] } Example: Get Page with All Fields Ask Cursor: "Get the page at /about/" { "id": 1050, "name": "about", "path": "/about/", "url": "/about/", "template": "basic-page", "status": 1, "statusName": "published", "parent": { "id": 1, "path": "/", "title": "Home" }, "numChildren": 5, "created": "2023-05-15T10:30:00+00:00", "modified": "2024-11-20T14:22:00+00:00", "fields": { "title": "About Us", "body": "<p>We are a team of dedicated professionals...</p>", "Images": { "_count": 2, "_files": ["team-photo.jpg", "office.jpg"] } } } Example: RepeaterMatrix Support The module fully supports RepeaterMatrix fields, returning the actual content with type labels: { "matrix": { "_count": 3, "_items": [ { "_typeId": 1, "_typeLabel": "Body", "Body": "<h2>Welcome to our guide</h2><p>This guide covers...</p>", "Images": null }, { "_typeId": 2, "_typeLabel": "FAQs", "faq_question": "What is the best time to visit?", "faq_answer": "The summer months offer the best weather for walking..." }, { "_typeId": 3, "_typeLabel": "Call to Action", "cta_title": "Plan Your Visit", "cta_link": "/contact/" } ] } } So thats the first part done and working. My next plan is to be able to 1. PULL / convert a databse page into a local text file which lists all page properties, fields, template etc 2. edit the file as a local text file 3 PUSH the text file back into PW so that the original content picks up the changes Just having fun and building something useful. Very likely there are similar solutions or better ways to handle this but this suits my workflow ATM. Cheers P
-
PromptWire 1.7.0 is out with 4 new MCP tools, bringing the total to 40. Latest tools are listed below. I haven't 100% fully tested a site sync yet, so always back up your existing site, database and files. pw_site_compare compares your local and remote sites across pages, schema, and template/module files. Pages are matched by URL path rather than database ID, so it works reliably across environments with different auto-increment sequences. You can exclude templates (e.g. user, role, licence pages) to focus the diff on what you actually intend to deploy. pw_site_sync (my favourite) orchestrates a full deployment in one operation: compare, back up the target, enable maintenance mode, push schema, push pages with their file/image assets, push template and module files, disable maintenance. It runs in dry-run mode by default so you see the full plan before anything is touched. Scope can be narrowed to just pages, schema, or files. pw_maintenance toggles maintenance mode on local, remote, or both sites. A styled 503 page is served to visitors with appropriate Retry-After and noindex headers. Superusers and the PromptWire API bypass it, so you can verify changes and keep the agent working during a deployment. pw_backup creates database dumps (using ProcessWire's WireDatabaseBackup) and zip archives of site/templates and site/modules. You can list, restore, or delete backups from either environment. The backup directory is auto-protected with .htaccess so SQL dumps are never web-accessible. HTTPS enforcement. The API endpoint rejects plain HTTP with a 403 before the API key is checked. PROMPTWIRE_ALLOW_HTTP in config-promptwire.php bypasses this for local dev only. Autoload change. The module is now autoloaded to intercept front-end requests during maintenance mode. The cost is a single file_exists() check per page load. Documentation updated across all pages at peterknight.digital/docs/promptwire/v1/
-
The memory feature is now active in AgentTools and it's enabled by default. I have to say it makes the experience a whole lot better. Also, we've updated it so that agents can now decide what API.md files they want to receive, and whether or not they want sitemaps/schema, rather than is sending stuff the may or may not need. So the "Include extra context" setting in engineer is now removed since it's no longer necessary. More coming by Friday.
-
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 .
-
This module allows you to automatically rename file (including image) uploads according to a configurable format This module lets you define as many rules as you need to determine how uploaded files will be named and you can have different rules for different pages, templates, fields, and file extensions, or one rule for all uploads. Renaming works for files uploaded via the admin interface and also via the API, including images added from remote URLs. Github: https://github.com/adrianbj/CustomUploadNames Modules Directory: http://modules.processwire.com/modules/process-custom-upload-names/ Renaming Rules The module config allows you to set an unlimited number of Rename Rules. You can define rules to specific fields, templates, pages, and file extensions. If a rule option is left blank, the rule with be applied to all fields/templates/pages/extensions. Leave Filename Format blank to prevent renaming for a specific field/template/page combo, overriding a more general rule. Rules are processed in order, so put more specific rules before more general ones. You can drag to change the order of rules as needed. The following variables can be used in the filename format: $page, $template, $field, and $file. For some of these (eg. $field->description), if they haven't been filled out and saved prior to uploading the image, renaming won't occur on upload, but will happen on page save (could be an issue if image has already been inserted into RTE/HTML field before page save). Some examples: $page->title mysite-{$template->name}-images $field->label $file->description {$page->name}-{$file->filesize}-kb prefix-[Y-m-d_H-i-s]-suffix (anything inside square brackets is is considered to be a PHP date format for the current date/time) randstring[n] (where n is the number of characters you want in the string) ### (custom number mask, eg. 001 if more than one image with same name on a page. This is an enhanced version of the automatic addition of numbers if required) If 'Rename on Save' is checked files will be renamed again each time a page is saved (admin or front-end via API). WARNING: this setting will break any direct links to the old filename, which is particularly relevant for images inserted into RTE/HTML fields. The Filename Format can be defined using plain text and PW $page variable, for example: mysite-{$page->path} You can preserve the uploaded filename for certain rules. This will allow you to set a general renaming rule for your entire site, but then add a rule for a specific page/template/field that does not rename the uploaded file. Just simply build the rule, but leave the Filename Format field empty. You can specify an optional character limit (to nearest whole word) for the length of the filename - useful if you are using $page->path, $path->name etc and have very long page names - eg. news articles, publication titles etc. NOTE - if you are using ProcessWire's webp features, be sure to use the useSrcExt because if you have jpg and png files on the same page and your rename rules result in the same name, you need to maintain the src extension so they are kept as separate files. $config->webpOptions = array( 'useSrcExt' => false, // Use source file extension in webp filename? (file.jpg.webp rather than file.webp) ); Acknowledgments The module config settings make use of code from Pete's EmailToPage module and the renaming function is based on this code from Ryan: http://processwire.com/talk/topic/3299-ability-to-define-convention-for-image-and-file-upload-names/?p=32623 (also see this post for his thoughts on file renaming and why it is the lazy way out - worth a read before deciding to use this module). NOTE: This should not be needed on most sites, but I work with lots of sites that host PDFs and photos/vectors that are available for download and I have always renamed the files on upload because clients will often upload files with horrible meaningless filenames like: Final ReportV6 web version for John Feb 23.PDF
-
Maybe some core files are missing on the server? It can happen that uploading a complete upload to a new server misses a file or something.
-
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.
-
Well, it's been about a year in the making, but v5 is finally available. I have upgraded a lot of sites to it now without issues, but I would still caution you to be ready to revert (or delete module) files if the namespace changes cause any issues - there were a lot early on. The two new banner features are: the console panel can now run very long running scripts (there is a one hour limit just so that broken scripts don't run forever) which is great for massive batch modifications or the like. The dumps recorder panel (either manually loaded or via Enable Guest Dumps) now live polls for new dumps so you don't need to continually load the page to see the entries as they are logged. Have fun! Breaking Changes - Minimum requirements bumped to ProcessWire 3 and PHP 7.1 - Removed legacy Tracy 2.5.x core branch and FireLogger support - Panel DOM IDs now include `ProcessWire-` prefix — update any custom CSS/JS targeting panel IDs Namespace Support - Full `namespace ProcessWire` support across all panels and POST processing files - Autoloader bridge for seamless non-namespaced to namespaced module migration - Third-party panels bridged automatically via `class_alias()` Security - Comprehensive security hardening: XSS sanitization, CSRF protection on all panels, directory traversal fixes, CSP nonces on all inline scripts, cookie SameSite enforcement, and input sanitization New Features - Console — long-running scripts automatically switch to background polling, surviving gateway timeouts; session locks released so you can continue browsing while scripts run; storage migrated to IndexedDB - PW Version Switcher — extracted into its own class with automatic version reverting on failure - Dumps Recorder live polling — live-polls for new dumps from other users and guest sessions - File Editor BlueScreen integration — exception page links open in the built-in file editor - Various smaller additions across Diagnostics, API Explorer, Request Info, and PW Info panels Bug Fixes - PHP 8.x compatibility fixes for `htmlspecialchars()`, `trim()`, and `isset` null handling - Fixed Console panel snippet and polling issues, including cache-busting for CDN/proxy environments - Fixed File Editor not opening files linked from BlueScreen exception pages - Fixed Adminer URL/namespace issues and thumbnail viewer path handling - Fixed Debug Mode panel to use modern API methods instead of deprecated ones - Fixed API Explorer "What's New" section and reflection errors for hooked methods - Fixed "unsaved changes" false positive when saving pages in PW admin - Windows path and line ending fixes Performance - Session lock contention reduced - Various loop and query optimizations
-
Good morning @da² I have wrapped my head around your problem and I have found a possibility to check file uploads against the value of an input field by using a custom validation. In the following example you have 2 form fields: Field 1 is a text input where you have to enter the name of a file (for example "myfile") without the extension Field 2 is the file upload field where you upload a ZIP folder with different files. Validation check In this simple example it will be checked if there is a file with the filename entered in field1 present inside the files uploaded in field 2. In other words: Check if the uploaded ZIP folder contains a file with the filename for example "myfile" in this case. If yes, then the form submission is successful, otherwise an error will be shown at field 1. This is only a simple scenario to demonstrate how a custom validation could be done. You have to write your own validation rules. Now lets write some code for this example: The first one is the custom validation rule. Please add this code to your site/init.php. If this file does not exist, please create it first. <?php namespace ProcessWire; \Valitron\Validator::addRule('checkFilenameInZip', function ($field, $value, array $params) { $fieldName = $params[0]; // fieldname of the upload field $form = $params[1]; // grab the form object // get all the files from the given ZIP folder as an array $zipfiles = $form->getUploadedZipFilesForValidation($fieldName); // now start a validation rule with the value of this input field ($value) and the files inside the ZIP folder if (!is_null($zipfiles)) { $fileNames = []; foreach ($zipfiles as $zipfile) { $fileNames[] = pathinfo($zipfile, PATHINFO_FILENAME); } return in_array($value, $fileNames); } return true; }, 'must be a wrong filename. The ZIP folder does not contain a file with the given name.'); I have named the validation rule "checkFilenameInZip" in this case. The important thing is that you have to add 2 values to the $params variable: The name of the file upload field The form object itself To get all the files that were uploaded via the upload field, you can use one of these methods of the form object. getUploadedZipFilesForValidation (outputs only files of a ZIP file) getUploadedFilesForValidation (outputs all files of a file upload field) Important: To use these 2 new methods you have to update to the latest version of FrontendForms (2.3.14)! All ZIP files will be extracted automatically. Nested ZIP files are supported too. Now lets create the form $form = new \FrontendForms\Form('testform'); $form->setMaxAttempts(0); // set 0 for DEV $form->setMaxTime(0); // set 0 for DEV $form->setMinTime(0); // set 0 for DEV $form->useAjax(false); // set false for DEV // input field containing a filename $filename = new \FrontendForms\InputText('filename'); $filename->setLabel('Name of file'); $filename->setNotes('Please enter the name of the file without extension.'); $filename->setRule('required'); $filename->setRule('checkFilenameInZip', 'fileupload1', $form);// here is the new custom validation rule: Enter the name of the file upload field as the first parameter and the form object as the second parameter $form->add($filename); // the file upload field $file1 = new \FrontendForms\InputFile('fileupload1'); $file1->setRule('required'); $file1->setLabel('Multiple files upload'); $form->add($file1); $button = new \FrontendForms\Button('submit'); $button->setAttribute('value', 'Send'); $form->add($button); if ($form->isValid()) { // do what you want // $extractedFiles = $form->getUploadedFiles(true); // returns all uploaded files including extracted ZIP folder files // $nonExtractedFiles = $form->getUploadedFiles(); // returns all uploaded files but Zip files are not extracted // $values = $form->getValues(); // returns all submitted form values } echo $form->render(); Copy this code and paste it inside a template for testing purposes. Upload a ZIP folder and add the name of a file inside the "filename" input field. If the file exists in your ZIP folder you will succeed. Please note: The extraction of the ZIP files is done only once during the validation process. You can get the extracted files afterwards inside the isValid() method via the getUploadedFiles() method from the temporary upload folder. Use the return of this method to do some more work with the uploaded files. Take this example as a starting point for your own validation rules and let me know if it works for you. I think this is the only possibility to get what you need. Another hint: User browser validation in FrontendForms to make client side validation first by enabling it in the backend.
-
PromptWire 1.6.0 is out with 7 new MCP tools for database inspection, log reading, and cache management. The total is now 36 tools. Latest are: DATABASE TOOLS pw_db_schema Inspect database tables. Without arguments it lists all tables with engines, row counts, and sizes. Pass a table name for detailed columns, types, keys, and indexes. Example output: table: pages columns: id int(10) unsigned PRI auto_increment parent_id int(11) unsigned MUL templates_id int(11) unsigned MUL name varchar(128) MUL status int(10) unsigned MUL modified timestamp MUL created timestamp MUL indexes: PRIMARY, name_parent_id (unique), parent_id, templates_id, status pw_db_query — Execute read-only SELECT queries. Only SELECT, SHOW, and DESCRIBE are allowed; mutations are blocked. A LIMIT is auto-injected if you don't include one. Example output: SELECT id, name, templates_id, status FROM pages ORDER BY id DESC LIMIT 5 id | name | templates_id | status 1703 | v1-15-4 | 72 | 1 1702 | test-05-brief-... | 59 | 1025 1701 | nyhavn-lufthavn | 59 | 1025 pw_db_explain — Run EXPLAIN on a SELECT query for performance analysis. Useful for diagnosing slow queries and confirming index usage. pw_db_counts — Quick overview of data volume. Shows row counts for core ProcessWire tables (pages, fields, templates, modules, caches) and the 20 largest field data tables. Example output: Core tables: pages: 605 | fields: 76 | templates: 53 | modules: 126 Top field tables: field_title: 599 | field_pkd_mediahub_mime: 254 | field_pkd_mediahub_image: 168 LOG TOOLS pw_logs — List available log files, or read and filter entries from a specific log. Filter by level (error, warning, info) and text pattern. Example output errors 138.5 KB 2026-04-20 exceptions 101.9 KB 2026-04-21 media-hub 38.4 KB 2026-04-21 modules 30.9 KB 2026-04-21 session 22.0 KB 2026-04-21 pw_last_error — Retrieve the most recent error from the error and exception logs. No parameters needed. Example output: 2026-04-21 15:47:56 [exceptions] SQLSTATE[42S22]: Column not found: 1054 Unknown column 'field_images.width' CACHE TOOL pw_clear_cache — Clear ProcessWire caches by target: all, modules, templates, compiled, or wire-cache. Useful after deploying changes or when things feel stale. Example output: target: modules cleared: [modules] success: true All 7 tools work via both local CLI and remote HTTP API, so they're available whether you're working against a local dev site or a remote production server.
-
Export your ProcessWire site structure as comprehensive, AI-optimized documentation for ChatGPT, Claude, Copilot, and other AI coding assistants. What It Does Context automatically generates complete documentation of your ProcessWire site in formats specifically optimized for working with AI: 📊 Site Structure Complete page hierarchy exported as JSON, TOON, and ASCII tree Shows all relationships, templates, URLs, and metadata Smart collapsing for large page lists 📋 Templates & Fields All template definitions with complete field configurations Field types, options, requirements, default values Special handling for Repeater, Matrix, Table fields 📦 Content Samples Real page examples exported for each template Shows actual data formats and field usage Helps AI understand your content patterns 💾 Code Snippets Customized selector patterns for your site type Helper functions and utility code API implementation examples 🤖 AI Prompts Ready-to-use project context file Template creation prompts Debugging assistance prompts Session continuity templates 🖥️ CLI Commands Export from command line for AI agents Query templates, fields, and pages directly Perfect for Claude Code, Cursor, Windsurf integration Dual Format Export (The Game Changer!) Context exports in two formats simultaneously: JSON Format Standard format for development tools, APIs, and compatibility TOON Format (AI-Optimized) ✨ Token-Oriented Object Notation designed specifically for AI prompts: 30-60% fewer tokens than JSON Significantly reduces API costs Same data, more compact representation No external dependencies - pure PHP Real Savings Example For a typical ProcessWire site with 50 templates: structure.json (15,000 tokens) → structure.toon (8,500 tokens) = 43% savings templates.json (8,000 tokens) → templates.toon (4,000 tokens) = 50% savings samples/*.json (12,000 tokens) → samples/*.toon (6,500 tokens) = 46% savings Cost Impact (Claude Sonnet pricing): JSON export: $0.105 per AI interaction TOON export: $0.057 per AI interaction Save ~$5/month if you use AI assistants 100 times/month Installation cd /site/modules/ git clone https://github.com/mxmsmnv/Context.git Then in admin: Modules → Refresh → Install Or download from ProcessWire Modules Directory Quick Start Web Interface Setup → Modules → Context → Configure Choose your site type (Blog, E-commerce, Business, Catalog, Generic) ✅ Enable "Export TOON Format" (recommended for AI work!) Enable optional features: ✅ Export Content Samples ✅ Create Code Snippets ✅ Create AI Prompts ✅ Generate SKILL.md for AI Agents Click "Export Context for AI" Files appear in /site/assets/cache/context/ CLI Interface # Full export php index.php --context-export # Export TOON format only (fastest, smallest) php index.php --context-export --toon-only # Query specific data php index.php --context-query templates php index.php --context-query fields php index.php --context-query pages "template=product, limit=10" # Quick stats php index.php --context-stats # Help php index.php --context-help Perfect for AI coding agents like Claude Code, Cursor, and Windsurf! Generated Files /site/assets/cache/context/ ├── README.md # Complete documentation ├── SKILL.md # AI agent skill definition ├── structure.json / .toon # Page hierarchy ├── structure.txt # ASCII tree ├── templates.json / .toon # All templates & fields ├── templates.csv # Templates in CSV ├── tree.json / .toon # Combined structure ├── config.json / .toon # Site configuration ├── modules.json / .toon # Installed modules ├── classes.json / .toon # Custom Page classes │ ├── samples/ # Real content examples │ ├── product-samples.json │ └── product-samples.toon # 46% smaller! │ ├── snippets/ # Code patterns │ ├── selectors.php # Customized for your site type │ ├── helpers.php # Utility functions │ └── api-examples.php # REST API examples │ └── prompts/ # AI instructions ├── project-context.md # Complete project overview ├── create-template.md # Template creation guide ├── create-api.md # API creation guide ├── debug-issue.md # Debugging helper └── project-summary.md # Session continuity template Using with AI Assistants Web Interface Upload Upload TOON files to save tokens and costs: 📎 structure.toon 📎 templates.toon 📎 prompts/project-context.md Then ask your AI assistant: "Help me create a blog post template with title, body, author, categories, and featured image. Follow the existing patterns from templates.toon" AI Coding Agents (Claude Code, Cursor, Windsurf) 1. Tell your agent to read the docs: Read /site/modules/Context/AGENTS.md 2. Agent can now export context: php index.php --context-export --toon-only 3. Agent queries specific data: php index.php --context-query templates 4. Agent reads exported files: Read SKILL.md, then structure.toon and templates.toon The AI has complete context of your site and can generate code that follows your exact patterns! Site Type Customization Code snippets automatically adapt to your site type: Blog / News / Magazine Post listings, author archives, category filtering Recent posts, popular content, related articles E-commerce / Online Store Product listings, cart logic, order processing Inventory management, payment integration Business / Portfolio / Agency Service pages, team members, case studies Testimonials, project galleries Catalog / Directory / Listings Brand hierarchies, category filters Advanced search, sorting, pagination Generic / Mixed Content General purpose patterns for any site type Features Overview Always Exported (Core) ✅ Complete page tree structure ✅ All templates with field definitions ✅ Site configuration and settings ✅ Installed modules list ✅ Custom Page classes ✅ README with complete documentation ✅ SKILL.md for AI agents Optional (Configurable) ⚙️ Content samples (1-10 per template) ⚙️ API JSON schemas ⚙️ URL routing structure ⚙️ Performance metrics ⚙️ Code snippets library ⚙️ AI prompt templates ⚙️ Field definitions metadata Advanced Settings Auto-update on template/field changes Custom export path (supports absolute paths) Maximum tree depth (3-20 levels) JSON children limit (prevent huge files) Compact mode for large lists Custom AI instructions CSS framework detection (or manual override) Why TOON Format? TOON is specifically designed for AI prompts. Here's the difference: JSON (verbose): { "products": [ {"id": 1, "title": "Dark Chocolate", "price": 12.99}, {"id": 2, "title": "Milk Chocolate", "price": 9.99} ] } TOON (compact): products[2]{id,title,price}: 1,Dark Chocolate,12.99 2,Milk Chocolate,9.99 Same data, 50% fewer tokens! Use Cases 🤖 AI-Assisted Development Upload your site context to Claude/ChatGPT and get code that follows your exact patterns 🤖 AI Coding Agents Claude Code, Cursor, Windsurf can export and query your site via CLI 📚 Developer Onboarding New team members get complete site documentation instantly 🔄 Site Migration Export complete site structure for documentation or migration planning 📖 Code Standards Maintain consistency across your team with AI that knows your patterns 💰 Cost Optimization Reduce AI API costs by 30-60% with TOON format 🔁 Session Continuity Maintain context between AI coding sessions with project-summary.md API Variable In your ProcessWire code: // Get Context module instance $context = wire('context'); // Programmatic export $context->executeExport(); // Get export path $path = $context->getContextPath(); Links GitHub: https://github.com/mxmsmnv/Context TOON Format Spec: https://toonformat.dev Screenshots Example Workflow Export your site Click one button or run php index.php --context-export Upload to AI Upload .toon files to Claude/ChatGPT for maximum efficiency Build features faster AI knows your exact site structure, templates, and patterns Save money Use 30-60% fewer tokens on every AI interaction Perfect for ProcessWire developers who use AI coding assistants! The TOON format support makes it significantly more cost-effective to work with Claude, ChatGPT, and similar tools. Now with CLI support for seamless AI agent integration! Questions? Suggestions? Let me know! 🚀
- 24 replies
-
- 13
-
-
-
TrackingScripts Module Manage and inject tracking scripts (Google Analytics, Google Ads, Facebook Pixel, custom code) into site pages, with optional PrivacyWire consent integration and robots.txt/llms.txt file management. Features Google Analytics (GA4) — inject gtag.js with Measurement ID Google Ads — inject gtag.js with Ads conversion ID Facebook Pixel — inject Pixel tracking code with noscript fallback Custom code — free-form textareas for any third-party scripts (head and/or body) PrivacyWire integration — when enabled, scripts are injected with data-category attributes and type="text/plain" so they only load after user consent robots.txt & llms.txt — edit and auto-generate both files from the admin; content is written to the site root on save Per-service controls — enable/disable, position (head or body), and consent category for each service independently ID validation — regex validation for GA (G-), Ads (AW-), and Pixel (numeric) IDs before injection Admin-only exclusion — scripts are never injected on admin or form-builder templates Files site/modules/TrackingScripts/ ├── TrackingScripts.info.php ← module metadata ├── TrackingScripts.module.php ← main module (hooks, script injection) ├── TrackingScriptsConfig.php ← module configuration (ModuleConfig) ├── ProcessTrackingScriptsConfig.info.php ← Process module metadata └── ProcessTrackingScriptsConfig.module ← admin UI for non-superusers Installation Copy the TrackingScripts folder into /site/modules/ In the admin go to Modules → Refresh, then install TrackingScripts Optionally install ProcessTrackingScriptsConfig — this adds a Setup → Tracking Scripts page that allows non-superuser roles to edit the configuration. Assign the tracking-scripts-config permission to any role that needs access. Configuration Go to Modules → Configure → TrackingScripts (superuser) or Setup → Tracking Scripts (any user with permission). Google Analytics Field Description Enable Activate/deactivate injection Measurement ID GA4 ID, e.g. G-XXXXXXXXXX Position Inject in <head> or before </body> PrivacyWire Category Consent category (default: Statistics) Google Ads Field Description Enable Activate/deactivate injection Ads ID e.g. AW-XXXXXXXXX Position Inject in <head> or before </body> PrivacyWire Category Consent category (default: Marketing) Facebook Pixel Field Description Enable Activate/deactivate injection Pixel ID Numeric ID, e.g. 123456789012345 Position Inject in <head> or before </body> PrivacyWire Category Consent category (default: Marketing) Custom Tracking Code Two free-form textareas for any additional third-party code: Custom Code — Head: injected before </head> Custom Code — Body: injected before </body> PrivacyWire Integration When enabled, all tracking scripts are rendered with PrivacyWire-compatible attributes: <script type="text/plain" data-type="text/javascript" data-category="statistics" class="require-consent" src="..."></script> This ensures scripts only execute after the user gives consent for the corresponding cookie category. Requires the PrivacyWire module to be installed and active. Robots.txt & LLMs.txt Edit the content of both files directly from the admin. On save, the files are written to (or removed from) the site root: /robots.txt — search engine crawler directives /llms.txt — LLM/AI bot directives If a textarea is left empty, the corresponding file is deleted from the site root. How It Works The module hooks into Page::render (priority 100) to inject scripts via str_replace on </head> and </body>. This means: No template modifications required Works on all front-end pages automatically Runs before PrivacyWire (priority 101), so consent attributes are in place when PrivacyWire processes the page The robots.txt and llms.txt files are written via a hook on Modules::saveConfig, triggered whenever the module configuration is saved from either the module config screen or the Process admin page. ProcessTrackingScriptsConfig (Admin UI) A Process module that mirrors the full TrackingScripts configuration under Setup → Tracking Scripts. Purpose Allows non-superuser roles to manage tracking scripts without access to the Modules admin. Permission The module registers the permission tracking-scripts-config. To grant access: Go to Access → Roles Edit the desired role Check tracking-scripts-config Save How it works Reads and writes the same configuration data as TrackingScripts via $modules->getConfig() / $modules->saveConfig() Changes from either location (Modules → Configure or Setup → Tracking Scripts) are reflected in both Saving triggers the same Modules::saveConfig hook, so robots.txt/llms.txt files are written automatically Requirements ProcessWire 3.0.110+ PHP 7.2+ PrivacyWire (optional, for consent integration) License Licensed under the MIT License.
-
- 5
-
-
Hey everyone, on a recent client project we had to deal with a large number of Markdown files that needed to end up as regular HTML content on ProcessWire pages. Converting them manually or piping them through external tools wasn't an option – too many files, too tedious, and the content had to be stored as actual HTML in rich textfields, not just formatted at runtime. So we built a small module that handles this directly inside ProcessWire. How it works The module creates a file upload field (md_import_files) and a Repeater field (md_import_items) with a standard title field and a richtext body field (md_import_body) inside. The body field automatically uses TinyMCE if installed, otherwise CKEditor. You add both fields (md_import_files,md_import_items) to any template, upload your .md files, hit save – each file gets converted to HTML via PW's core TextformatterMarkdownExtra and stored as a separate Repeater item. The source filename goes into the items title, processed files are removed from the upload automatically. Template output The Repeater items are regular PW pages, so output is straightforward: foreach ($page->md_import_items as $item) { echo "<section>"; echo "<h2>{$item->title}</h2>"; echo "<div>{$item->md_import_body}</div>"; echo "</section>"; } Tag mappings One thing we needed right away: control over how certain Markdown elements end up in HTML. For example, #headings in Markdown become <h1> – but on most websites <h1> is reserved for the page title. The module has a simple config (Modules → Configure → Markdown Importer) where you define tag mappings, one per line: h1:h2 h2:h3 strong:b blockquote:aside hr:br This performs a simple 1:1 tag replacement after conversion, preserving all attributes. Works well for standalone or equivalent elements like headings, inline formatting, blockquotes, or void elements like hr:br. Note that it doesn't handle nested structures – mapping table:ul for example would only replace the outer <table> tag while leaving thead, tr, td etc. untouched. Requirements ProcessWire 3.0.0+ FieldtypeRepeater (core) TextformatterMarkdownExtra (core) GitHub: github.com/frameless-at/MarkdownImporter Modules Directory: https://processwire.com/modules/markdown-importer/ Happy to hear if anyone finds this useful or has suggestions for improvements. Cheers, Mike
-
- 6
-
-
-
File Mover Allows the selection of files or images for moving or copying to a different field. The destination field can be on the same page as the source field or on a different page. Screencast For convenience in the screencast this demonstration shows moving and copying images and files between fields on the same page, but you can also move/copy between pages by following the same process. Usage In any Images or Files field, hover on the field label to show the File Mover icon. Clicking on the icon will reveal the File Mover buttons. If no items are yet selected you'll see a button labelled "Select items to later move or copy". Click the button to enter selection mode. While in selection mode, click one or more images/files to select them. When you have finished selecting items click the "Done" button. Note: you can only select from one Images and one Files field at a time before completing the move/copy step. In the Images or Files field that you want to move/copy the items to, hover the label to show the File Mover icon and click it to reveal the File Mover buttons. When you click the Move or Copy button the page will automatically be saved and the selected items will be moved or copied accordingly. There is also a button to clear the current selection if needed. If you hover on any of the buttons a tooltip shows the filenames of the currently selected items, in case you need a reminder. Configuration There is a field in the module config that defines which roles are allowed to use the File Mover module. If the field is left empty then all roles are allowed. https://github.com/Toutouwai/FileMover https://processwire.com/modules/file-mover/
-
I have trouble following the instructions here. I have several virtual domains successfully installed on Apache Ubuntu, with index.php placeholder files. I want to install Processwire sites in each of them, each with their own config and database, and have all of them use the same /wire codebase in /domainone. /www/html/domainone/site (+ /wire) /www/html/domaintwo/site /www/html/domainthree/site etc. The 'how to install' instructions on the link talk about creating subdirectories off the web root, tmp directories, moving and renaming folders, having to come up with different names for the site folders, etc. It does not sound like what I am looking for. Is there a simpler way to get the structure described above? ChatGPT claimed I could just bootstrap the /wire folder into the other with one line and install normally, but that did not work of course.
-
Hello again! I'm moving my website: Norwegian to Polish dictionary http://lizus.net (ca. 28k pages) from Glossword to ProcessWire. Everything looks great except one thing: for each page there is created empty subfolder in site/assets/files. So I've got ca. 28k empty and unused folders which takes around 4kB each on my server (sic!). The same situation is with site/assets/cache :/ Is there any solution to improve this? Best regards, Remi Turala.
-
Hi, thanks for the explanation. I'll try to explain my actual case, it's more complex and would be easier to manage if I could just invalidate a form field manually. The form has a zip field, zip file contains several files. To make the explanation short, I extract the zip and validate its content, but the validation also requires to know values from the other fields in the form. I think this is the first problem: accessing the other fields (only if they are validated by the form) while validating the zip field. So I have a field "A", form should valid it (basic validation, like "required"), then while validating zip content (another field) and extracting its data I check again field "A" against the zip data. If there's an issue I must invalidate field "A" (but I'm processing zip field) and display an error on this "A" field. In one sentence: I know that field "A" is wrong only when processing zip field. Also this validator seems to make things more complex. I don't want to extract/validate the zip 2 times, once in validator, and once again when form is fully validated (isValid() function) to save values in database. So I should refactor code to store the validated data somewhere to reuse them in isValid() function, this is extra complicated work I want to avoid. Also this is not a light process for CPU, zip can be 500 MB and contain a lot of files to parse (some are also zip to extract again), this is a heavy process and I don't like the idea of doing this 2 times. Telling the form "this field is not valid and should display this error message" is way more simple and doesn't necessitate extra code, I just have to return an error code from the high level class that process data and invalidate one or another field. If you have an idea to avoid processing zip 2 times and without adding extra work, it would be welcomed, hope my explanations are clear (I'm not sure 😆). Actually I'm displaying error message on form result page, and in case of error the user has to fill again the whole form. My client is OK with this since this form is mainly used by himself, but I'm not happy because this is also a public form that a user could use, and having the whole form reset in case of error is a bad design. This is the actual code in template php: $form = new UploadSolveForm(); if ($form->isValid()) { $uploadFilePath = $form->getSolveFilePath(); $tagsText = trim($form->getValue('tagsFinalInput')); $tags = $tagsText ? explode(',', $tagsText) : []; $parseCommand = new ParseSolveUploadCommand( $uploadFilePath, trim($form->getValue('groupName')), $tags, RoomType::from($form->getValue('room')), boolval($form->getValue('isSpaceKo')), $form->getValue('subtreeVilainPosition') ? TablePosition::from($form->getValue('subtreeVilainPosition')) : null, $form->getValue('subtreeVilainAction') ? PlayerAction::from($form->getValue('subtreeVilainAction')) : null, $form->getValue('subtreeHeroPosition') ? TablePosition::from($form->getValue('subtreeHeroPosition')) : null ); if (!$parseCommand->execute()) { // I would like to do: // $errorCode = $parseCommand->getErrorCode(); // if ($errorCode == ParseSolveUploadCommand::SOME_ERROR){ // $form->setError('a field name', 'errorMessage'); // } else if($errorCode == ParseSolveUploadCommand::SOME_OTHER_ERROR){ // $form->setError('another field name', 'errorMessage'); // } if ($parseCommand->getUserErrorMessage()) NoticeManager::add($parseCommand->getUserErrorMessage(), NoticeType::ERROR); // Will display an alert box on form result page else NoticeManager::add(__("Une erreur s'est produite, merci de contacter un administrateur.", COMMON_TRANSLATION_DOMAIN), NoticeType::ERROR); } else { $parseCommand->getReport()->noticeUser(); } } Thank you for your interest. 🙂
-
@gebeer Do you think the AgentTools module should be using this SKILLS.md file rather than its CLAUDE.md and agent_cli.md files ? I had asked Claudia about if we should be using an AGENTS.md file rather than a CLAUDE.md file, but she seemed pretty definitive with what she thought was best, and said other agents would be fine so long as they were directed to the file. The CLAUDE.md file does automatically pull in the agent_cli.md for Claude at least. But if we can save the user or agents (of any kind) a step just by using a SKILLS.md file, that sounds preferable to me. So far the Claude I'm using hasn't wanted to use SKILLS.md files. I gave it the whole processwire-knowledgebase repo that's full of SKILLS files and it read through all of them and said it was good, but said it preferred to derive this info from the core files directly instead. Btw, can you share an example of one of the schema files you were mentioning before? Sounds like maybe another thing that should be in the module? Can you tell me more? @psy Aww, this is really nice. Thanks. 🙂 @szabesz Wow this sounds like a really good deal. I wasn't thrilled with spending $20/month for Claude Code either, but I was starting to spend more than that with the pay-as-you-go plan, so it just made sense. The tokens apparently go farther with the subscription plans than the pay-as-you-go. I hear people using Opus are quickly hitting some kind of limits (like in minutes) so I've just stuck with Sonnet so far. It's not perfect, but I'm pretty happy with the results. Plus I've not hit any limits with it yet, despite using it all day. But if I ever needed more resources, the Max plans wouldn't be an option for me, so I should probably start getting familiar with the other options available. @psy Can you tell me more about the phpstorm integration? Claude Code doesn't seem to have anything significant in terms of phpstorm integration. There's a plugin, but it doesn't seem to me like it does much. While I'm not sure I need any kind of phpstorm integration just yet, I'd be curious to know more about it. @Ivan Gretsky In this case of the AgentTools module, the AI is required to create the migrations, but not to apply them. I'm assuming most wouldn't have an AI agent on their web server. Good point. Should I be concerned that mine isn't all that generous with the compliments? I get some "this is a well structured file" and lists of "the good" and "the bad", and I get a lot of pushback. Though to be honest I like the directness, honesty and pushback from Claude. Somehow coming from an AI, it's always easy to accept compared to getting pushback from a person. 🙂
-
Recommended way to move installation from local to production server
da² replied to _Roy_'s topic in Getting Started
My way to upload local site to production is using basic Linux tools. I'm on Windows but with a WSL console running Debian, you have access to all tools. First I use Ant to synchronize files from my code to a separate local directory (Ant is a set of tools that you use by creating a simple XML describing tasks), I exclude some files or directories (like PW cache and logs...), automatically increment a version number, replace some variables in some files (like site version/build number, debug variables...)... Then an rsync command send the code to the server. Deploying a site update is then only executing this script and waiting a few seconds. 🙂 I exclude site config.php because some values change from production server (database credentials, debug=false|true, Stripe identifiers...). Example of a deploy script (very simple but powerful stuff ^^): #!/bin/bash # Directory where Ant project synchronize files. build_dir=./target/processwire/ echo "=> Executing ANT project..." cmd.exe /c build-processwire.bat buildOk=$? if [ $buildOk -ne 0 ]; then echo "ANT build failed, stopping script." exit fi read -p "Press enter to update PRODUCTION server, ctrl+c to cancel." echo "=> Copying files to web server..." rsync -avh --delete-delay --include-from=deploy-includes.txt --exclude-from=deploy-excludes.txt --chown=linux_user_name:linux_user_group -e ssh $build_dir linux_login@server_ip:/var/www/path_to_productionsite/ If this is the first time I deploy this project, or if this is a staging server (not production) and I want to reset database at every deploy, I add this line, it copies my local PW database to the server: echo "=> Copying database to production" /mnt/e/xampp_php8.2/mysql/bin/mysqldump.exe --add-drop-database -uDB_local_user --databases database_name | ssh linux_login@server_ip "mysql -uDB_user -pDB_password" Since Ant project uses files synchronization (and not copy) and rsync does the same, it deploys to server only files that have changed since last deploy, so it's fast and console logs are clear. If you're interested with Ant XML file, I can show you an example. Same with directories/files I exclude from build, both in Ant and rsync. EDIT: For the first installation on server, I start with a regular PW install directly on server. EDIT 2: Script also set the site in maintenance while deploying, displaying a "Maintenance, please come back later" message, with a bit of Apache configuration. It uploads a file at the site root on server, and when this files exists Apache redirects users to a basic HTML page (except for my IP). -
I tweaked the new Admin Theme KONKAT a bit to my needs via the config files provided and using it now for three months or so. Now it is the opposite. When I work an older projects, I wish to switch to the new layout. Changing stuff you are used to for months/years will take time to adopt. Now I couldn‘t even tell what exactly bothered my with the initial KONKAT theme when I tried it the first time. So for me it was just the human reflex BAHH looks different, want my old design back.
- 52 replies
-
- 2
-
-
@ukyo Thanks for your awesome work with those modules, really impressive what you are building, and it's a big help for improving the AI friendliness of ProcessWire. The AgentTools module readme is now linking to your boost project as well. Glad you like the API.md files. Admittedly it was not my idea, but I asked Claude what would be helpful and he said these API.md files, plus an abbreviated sitemap json file so that it can get a big picture overview of a PW installation at a glance. That sitemap feature was actually added to the AgentTools module today. Several API.md files have been added to the core today as well. For Fieldtypes that don't have their own directory, they are in a combined /wire/modules/Fieldtype/API.md file. We're also adding dedicated Field classes specific to each Fieldtype, which will improve field documentation but also allow for custom field API methods separate from the Fieldtype (where useful).
-
@gebeer Thanks! Sounds like Claudia would like a PR for the ddev update if available. For the skills stuff, thanks for explaining it all. I'll look forward to having a closer look in your commits but one thing noticed so far is that your version has the option to install the skill files off the PW install root in a .agents dir. But the only place PW an assume is writable is off the /site/assets/. So the .agents off the root would work in some installs and not in others. Thanks for the example rock migrations file. It looks to me like the same format that the core Pages Export/Import module uses, except that the module uses them JSON encoded. While we're calling the ones generated by Claude with the AgentTools module "migrations", they really are just repeatable logic. So the logic can be about creating/updating/deleting some pages/fields/templates or perhaps something else. How does the Rock Migrations format work when you need some logic as part of the migration, such as creating a page, then creating another page that references that page (FieldtypePage)? You could do this with the pages export/import but would have to run the JSON through more than once to do it. Also, how does it handle files? Handling files is something AgentTools does not yet do.
-
Hey folks, we at frameless Media often develop across multiple devices – laptop, tablet, sometimes even from a phone with an AI coding assistant. Git is our single source of truth, but getting those changes onto a staging or production server has always been annoying. Especially on shared hosting where there's no SSH, no git, and git-based FTP via YAML configs is more hassle than it's worth. We also frequently need to test new modules directly on shared hosting environments where the server setup differs from our local machines. Manually uploading files after every push? No thanks. So we built GitSync. 🎯 TL;DR: ✅ Link any installed module to its GitHub repo ✅ See all branches and their latest commits ✅ One-click sync – only changed files are downloaded ✅ GitHub Webhook support – auto-sync on every push ✅ Works on shared hosting – no git, no SSH, no cron ✅ Private repo support via GitHub Token What's the difference to ProcessUpgrade? ProcessUpgrade is great for updating published modules from the PW modules directory. But it tracks releases, not branches. During development, when you're pushing to `develop` or `feature/xyz` ten times a day, you need something different. That's where GitSync comes in. 🚀 How it works Install the module, add your GitHub Token (optional for public repos) Go to GitSync > Add Module, pick any installed module from the dropdown GitSync searches GitHub for matching repositories automatically Link the module to a repo + branch – done From now on, you can sync with one click. GitSync compares file hashes locally and remotely (using the same SHA1 blob hashing that git uses internally) and only downloads what actually changed. No full re-downloads, minimal API usage. Want it fully automatic? Set up a GitHub Webhook – enter a secret in the module config, point the webhook to `https://yoursite.com/gitsync-webhook/`, and every push triggers an automatic sync. The module overview shows a ⚡ webhook badge on auto-synced modules so you always know what's wired up. The real power: remote development with AI 📱 You're on the train, phone in hand, chatting with Claude via the Claude app. Claude writes code, commits to a feature branch on GitHub. GitSync picks up the webhook and syncs the module to your dev server. Automatically. You open the edited webpage on your phone, check the result, give feedback, iterate. The entire development loop without ever opening a laptop. 🤯 This works just as well for teams: multiple developers push to GitHub from different machines, and the staging server always reflects the latest state – no manual deploys, no SSH sessions, no FTP. We've been using a prototype internally for a few weeks now and it's become part of our daily workflow – especially the webhook auto-sync is something we don't want to miss anymore. As proof of concept we built the public release entirely as described above 😃 Technical details for the curious The differential sync works like GIT itself: every file's content is hashed as `sha1("blob {size}\0{content}")`. GitHub's Trees API returns these hashes for the entire branch in a single request. GitSync computes the same hash locally. Matching hash = identical file = skip. Requirements ProcessWire >= 3.0 and PHP >= 7.4 with cURL Module and Docs 👉 GitHub: https://github.com/frameless-at/GitSync 👉 Module Directory: https://processwire.com/modules/git-sync/ Would love to hear your thoughts, ideas, and edge cases we might not have considered! Cheers, Mike
- 1 reply
-
- 11
-
-
-
Cre8aplace -- ProcessWire Showcase Overview Cre8aplace is a full-featured social networking platform built entirely on ProcessWire 3.0.255, PHP 8.4, and plain JavaScript/HTML/CSS. No frontend frameworks, no build tools, no bundlers. It delivers the core experience people expect from a social platform -- profiles, messaging, groups, newsfeeds, marketplace, blogs -- while prioritizing user privacy and data control. The site is currently in soft launch at [cre8aplace.com](https://cre8aplace.com). I started this project over five years ago and was recently revived with the availability of time (I'm retired). The goal was to build a platform where users own their experience -- where no data is sold, no algorithms manipulate what you see, and the platform exists to serve its members rather than advertisers. I've used ProcessWire for many years on various projects so it was the natural foundation. Ryan's philosophy of minimal opinions and maximum flexibility aligned perfectly with my project that needed to bend a CMS into something it wasn't explicitly designed for -- a real-time social networking platform. Where other CMSs would have fought at every turn with rigid content models and opinionated routing, ProcessWire simply got out of the way. A major thank you to Ryan! What ProcessWire Solved User Management Without a Separate Database ProcessWire's user system eliminated the need for a standalone user table entirely. Each member account carries approximately 50 custom fields -- everything from profile settings and privacy controls to notification preferences and subscription status. PW's field system handles all of this natively, with no separate profile table, no ORM, and no migration headaches when fields change. The Entity Page Tree Every entity type on the platform -- Groups, Pages (business presence), Events, Markets, Blogs, Movies, Games -- lives as a ProcessWire page with its own template. The page tree gives a natural hierarchy: /groups/ /groups/hiking-enthusiasts/ /groups/local-music/ /pages/ /pages/joes-coffee/ /markets/ /markets/handmade-goods/ Each entity type gets its own template file, its own toolbar configuration, and its own permission rules, but they all share the same rendering components (page header, toolbar, left aside, right aside). ProcessWire's template system made this architecture effortless. Flexible Templating as a Router I used ProcessWire's template system not as a traditional page renderer, but as a thin router/dispatcher. A single 'profile.php' template handles 12 different operations (timeline, account settings, privacy, messages, friends, membership, etc.) by dispatching to focused include files. Each include contains paired display and process functions. PW's template prepend (_init.php) bootstraps every request with shared utilities, configuration, and security setup. Admin Backend for Free ProcessWire's admin interface provided a complete backend without building one. SVG icons are stored as PW pages (37 icons, each with SVG markup in a textarea field). Site-wide configuration lives in a JSON field on a settings page, accessible from both PHP and JavaScript. Reaction types, emoji sets, and content categories are all managed through PW's admin. Authentication Built on Solid Ground Rather than building authentication from scratch, I extended ProcessWire's session and user system with custom email verification, token-based password reset, persistent remember-me login, and bot prevention. PW handles the password hashing, session management, and role assignment while I built the user-facing flows on top. Role Hierarchy The platform's permission system maps directly to ProcessWire roles: member < moderator < owner < administrator < superuser PW's role checking ($user->hasRole(), $user->isSuperuser()) powers everything from toolbar visibility to AJAX endpoint authorization. Group moderators and page employees add entity-scoped permissions on top of the global role hierarchy. Custom ProcessWire Modules I built four custom modules over the years while working on various other projects that extend ProcessWire's capabilities: - ViridiaCaptcha -- Bot prevention for registration and authentication forms. Integrates directly with PW's form processing. - ViridiaCalendar -- A full event calendar with RSVP tracking, recurring events, ICS, and profile-level event management. Renders calendar views and manages event CRUD through ProcessWire's module API. - ViridiaTicketing -- Support ticket system for user-to-admin communication. Built as a PW module with its own database tables and admin interface. - ViridiaGovFeed -- An automated government legislative feed that fetches federal bills from Congress.gov, executive orders and rules from the Federal Register, and state-level legislation via the LegiScan API. It publishes curated topics to a system-owned Page entity. Users follow the page to see legislative headlines in their newsfeed. Runs on a 6-hour cron cycle with deduplication and fetch logging. Architecture Highlights Hybrid Data Model I use ProcessWire pages for entities and users, but custom MariaDB tables for high-volume relational data. I created over 20 custom tables to handle topics, comments, replies, messages, relationships, reactions, notifications, marketplace orders, product variants, reviews, and more. This hybrid approach gives me the best of both worlds -- PW's flexible page management for entities, and direct SQL performance for data that sees thousands of writes per day. Encrypted Control Parameters What I am most proud of is no database ID ever appears in a URL, query string, or HTML attribute on this platform. Every sensitive identifier is encrypted with AES-256-CBC using ProcessWire's private key, passed as an opaque 'data-param' attribute, and decrypted server-side before processing. JavaScript never sees a real ID -- it just passes encrypted blobs back to the server. This is the backbone of the platform's privacy guarantees. AJAX-Driven Interface The platform runs over 50 AJAX actions across 8 endpoint routers. Every interactive operation -- posting a topic, sending a message, toggling a reaction, managing a marketplace order -- happens through AJAX calls that return JSON with server-rendered HTML. No client-side templating, no virtual DOM, no state management library. The server renders the HTML, the client inserts it. Single CSS File, No Framework The entire platform's styling lives in a single 132KB CSS file. Layout uses CSS Grid (3-column desktop, single-column mobile). Theming uses OKLch color space with CSS custom properties -- users pick an accent color via a color picker, and the entire UI adapts through computed color relationships. No Bootstrap, no Tailwind, no preprocessor. No Build Pipeline I wrote 23 JavaScript files as plain ES6+ and served directly. For production, a PHP-based minifier (matthiasmullie/minify) compresses them, and a GUID generator creates randomized filenames for cache busting. The entire "build" process is three shell commands. No webpack, no Vite, no npm. External Dependencies The platform uses only three external dependencies: 1. SunEditor (MIT license) -- WYSIWYG editor for blog posts. The only client-side library on the platform. Everything else is hand-written JavaScript. 2. Stripe PHP SDK -- Powers three subscription tiers (Free at $0, Standard at $8.95/month, Extended at $16.95/month) and marketplace seller payouts via Stripe Connect Express. The SDK is vendored directly in the project -- no Composer. 3. Wasabi S3 -- Cloud object storage for album images and marketplace product photos. Accessed via direct S3 API calls from PHP. Everything else -- UI components, animations, form handlers, real-time polling system -- is custom PHP, JavaScript, HTML, and CSS. Platform Features Social Core - User profiles with visitor viewing via opaque GUID URLs, granular privacy controls, and friend relationships with request/accept/block workflows - Newsfeed aggregating activity from friends, followed groups, pages, and markets - Topic creation with multi-image upload (adaptive grid layout, each with caption and tags), comments, replies, reactions, emoji picker, @mentions, link previews, and topic sharing - Real-time messaging supporting direct messages, multi-user conferences, group chat, page staff chat, system announcements, and moderation incident channels - Notification system with smart aggregation (groups repeat members until read), role-based filtering, and infinite scroll - Carousel view of multiple images with editing Community Entities - Groups with public/private/secret visibility, moderator roles, member approval, post approval queue, rules display, and ownership transfer - Pages (business presence) with follower system, employee roles, and staff-only chat channels Premium Features (Subscription-Gated) - Marketplace with Stripe Connect seller onboarding, product variants, shopping cart, checkout, digital downloads via pre-signed S3 URLs, order management, revenue reports, verified-purchase reviews, wishlists, and market following with new-product notifications - Blogs with SunEditor WYSIWYG, full CRUD, and newsfeed integration - Movies and Games as browseable entertainment entity types - Events powered by the ViridiaCalendar module with RSVP tracking - Schedule future topic postings - State-level government legislation tracking (3-state limit on Standard, unlimited on Extended) Administration - Admin panel with a reusable data table module -- a config-driven system where new admin tables require only a configuration array. Six tables migrated: Users, Groups, Pages, Categories, Reactions, Reports - Content moderation with report handling, per-report discussion channels, and moderator action tracking - Government legislative feed management with force-fetch and status monitoring By the Numbers | Metric | Value | | ------------------------- | ---------------------- | | Custom database tables | 20+ | | SQL migration files | 46 | | Custom user fields | ~50 | | JavaScript files | 23 (~500KB source) | | CSS | 1 file (132KB) | | PHP include files | 50+ | | Custom PW modules | 4 | | Utility functions library | 113KB (_functions.php) | | AJAX actions | 50+ | | npm dependencies | 0 | | Composer dependencies | 0 | | Build steps | 0 | Technology Stack - CMS: ProcessWire 3.0.255 - Language: PHP 8.4 - Database: MariaDB 15.0+ on Debian 12 - Server: Apache 2.4 - Frontend: Plain JavaScript (ES6+), HTML5, CSS3 - Payments: Stripe (subscriptions + Connect marketplace payouts) - Storage: Wasabi S3 (images) - Editor: SunEditor (blog WYSIWYG) - Frameworks: None - Build tools: None
- 2 replies
-
- 12
-