The context

I have a URL schema that is either doing something new, via a custom Route (no entry set):


…or reviewing an existing entry:


My custom route is working fine in that it's routing to the correct template logic when no entry is set.

I have some development in progress which will get converted to working with the PHP session – and therefore a properly integrated action URL – but for now I'm just invoking a template-derived action URL on a hard-coded <form> tag to test front-end behaviours.

The problem

The template renders fine and the front end behaves fine in all respects in my local dev environment, but…

In the staging environment only, something like the following is getting auto-added after my opening form tag (not by any code I have written):

<input type="hidden" name="p" value="bookings/new/accommodation">

…and the form will not submit to the next stage of the booking path that's invoked by the action URL.

I infer this is because it is determined to be a 404, even though the path matches the custom route and the expected template is being invoked.

As far as I can see the local and staging environments are near identical:

  • Both are running 4.3.6, albeit with:
    • Minor differences in host PHP
    • My local dev is Nitro and therefore using mariadb
  • Both have the same plugins and modules

Any clues as to what is going on?

  • My bookings channel section has a URI format of bookings/{slug} and calls bookings/_entry
  • My custom route is bookings/new/* and calls bookings/_entry


This feels like a work-around, not really a fix:

  • It seems that when a custom route is registering as a 404 (still don't know why that's the case here), there is a p parameter in craft.app.request.queryParams even when it is not explicitly in the URL.
  • And it was getting rendered because I had…
{% set qParams = craft.app.request.queryParams ?? null %}


<form …>
    {# Pass params along in the URL but 
       exclude ones that have fields submitted in this page
    {% set excludes = ['array', 'of', 'names'] %}

    {% for key, val in qParams %}
        {% if key not in excludes %}
            <input type="hidden" name="{{ key }}" value="{{ val }}">
        {% endif %}
    {% endfor %}

…so I have tweaked that with:

{% for key, val in qParams %}
    {% if key not in excludes and key != 'p' %}
        <input type="hidden" name="{{ key }}" value="{{ val }}">
    {% endif %}
{% endfor %}

…which works.

Revised framing of the question

I would still like to know why there is a 404 registering in the first place.

Is it perhaps that when entry is not defined that equates to a 404 even if a custom route picks it up?

1 Answer 1


You must be running Apache on staging, but not on dev, and the p param is getting added by the mod_rewrite rule in .htaccess.

p is the default pathParam config setting value. When present, it will be used to define the requested URI path.

  • Thanks. Staging is Apache and I am running Nitro on dev (haven't got with the DDEV program yet.) So is it the case that when entry remains unset there is kind of a soft 404 that persists? Commented Jan 23, 2023 at 16:18
  • You lost me there. entry will only be set if the template was loaded via a section’s Entry URI Format setting. If the template is assuming entry is set then you might get a 500 error, but not a 404. Commented Jan 23, 2023 at 17:00
  • No worries. I should probably provide more template context to make it clear what seems to be happening in my environment, but it’s not high priority. So thanks for your time today on this and my other thread. Commented Jan 23, 2023 at 17:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.