How to include a PHP form in HUGO

I recently needed to include a PHP form in one of my HUGO projects and found myself struggling with this task quite a bit. Some Google searches and within the forum gave me the impression I’m not the only one having difficulties with PHP forms within HUGO. So I thought I’ll post my solution here as a step-by-step guide. Maybe it can serve as a starting point for your project. However, I’m not a super-dev therefore my solution might not the optimum one can come up with and, in addition, English is not my first language. This said, please excuse me for language mistakes and please don’t hesitate to give feedback on the coding.

1. Make HUGO render .php files

HUGO ships with some default Media Types and Output Format Definitions but unfortunately PHP is not among it. As our form requires a .php file we need to setup custom Media Type and Output formatting for it.

Go to your config.toml and set up a Media Type for PHP:

# examplel for .toml
[mediaTypes]
  [mediaTypes."application/x-php"]
  suffixes = ["php"]

Based on this you may now define your output format in config.toml:

# example for .toml
[outputFormats]
    [outputFormats.PHP]
        mediaType = "application/x-php"
        isHTML = true
        baseName = "index"

2. Set output formatting in page front matter

Basically you are now (almost) ready to render pages as .php files. In my case I had a dedicated page that I wanted to hold the form:

|_ content
    |_ form
        |_ index.md

So I decided to set output formatting in front matter:

+++
title = "form"
outputs = ["PHP"]
+++

3. Create templates for PHP

Having set all this will output nothing until you’ve created a template to render your file.

As my page is a regular page it gets rendered with layouts/default/single.html. Duplicate this file and name it single.php.php (see the docs for more information):

|_____default
| | |____single.html
| | |____single.php.php

However, that won’t give you what you want if you have a baseof.html file in your layouts/_default directory. In this case you also need to duplicate this as single.php.php depends on it. Finally, this is how your _default dir should look like:

|_____default
| | |____single.html
| | |____baseof.php.php
| | |____list.html
| | |____baseof.html
| | |____single.php.php

Now calling hugo will render content/form/index.md as index.php instead of index.html (Unneccessary to say: you won’t be able to see your file content under localhost:1313 as HUGO’s local webserver is not capable of PHP. From here on you need either a local webserver with PHP (I use Apache in a docker container) or you need to push to a hosted environment with PHP in order to see the effect of content changes etc.).

4. Create a PHP form

My form was rather complicated. So I used the highly recommended form generator by Werner Zenk to create it. The tool returns all required logic and markup in one single .php file. So i decided to use it in my template “as it is.” (If you search for similar solutions with HUGO on the internet you’ll find approaches with shortcodes in .md files. I won’t discuss this but it’s an equivalent valid approach and depending on how you create your form it might be a better or less good fit).

In my case I saved the form as form.php under static/php/:

|____
| |____php
| | |____form.php

5. Include the form in your template file

Now, how do you include the form in your single.php.php file? Partial function won’t help you as your file lives under static and you can’t stop HUGO escaping your PHP code. But HUGO’s readFile function gives us what we want. Together with safeHTML is returns the PHP code as it is. This is what the template looks like in my project:

{{/*single.php.php*/}}
{{ define "main" }}
{{ .Content }}
{{ readFile "static/php/form.php" | safeHTML }}
{{ end }}

And bam!, here you are. HUGO renders your PHP code nicely and untouched to an index.php file. If your PHP code is error free your form should work now.

However, please note: the --minify flag doesn’t play nice with .php files. In my case HUGO erased some of the PHP code so I had to waive --minify. (If somebody knows a way to use --minify with the approach outlined please comment!)

Hope this tutorial makes including forms in HUGO a little bit more fun :wink: Comments are very welcome.

15 Likes

Nice write up!

Some tips:

If you simply want to include a PHP file you can put in in “static” and it will be copied over as is.

Instead of creating a “baseof.php.php” you can skip declaring {{ define “main” }}…{{ end }} in “single.php.php”.

Instead of using “readFile” you can put the PHP code directly in “single.php.php” by using “safeHTML” like this “{{ “<?php” | safeHTML }}” whenever your code has “<” or “>”.

This way you can insert template variables in the code, really useful sometimes.

2 Likes

If you also set isPlainText = true the templates will be parsed and executing using Go’s text template package, which is much more lenient when it comes to HTML escaping.

3 Likes

Thanks @bep, isPlainText makes it so safeHTML is not needed, not in me cases at least.

1 Like

Yes, with isPlainText there will be no need for safeHTML etc. But is probably fine when you are in full control of both your templates and data (or, which is its prime use, it’s not HTML that is to be executed by the browser, e.g. JSON).

@frjo @bep Many thanks for your kind feedback. I’ve tried both tips in my code and it improved things a lot (best: --minify can be used again, though it leaves single.php.php untouched - as it should be). Unfortunately I can’t edit my original post. So whoever may come across this tutorial please incorporate the feedback below the original post to give your code an improvement.

Thank you for sharing your know-how here. Your guide works perfectly. Nobody who knows Hugo wants to do without the advantages. But there are areas in which the dynamics of PHP are simply necessary.

2 Likes

Playing with it and works, I get working *.php files (I don’t use hugo server though, I upload and watch live).

However, I don’t not get styles in *.php files.

In /partial/head.html I have:

  {{ $styles := $styles | minify | fingerprint | resources.PostProcess }}
  <link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Integrity }}"/>

In the *.html output they render fine as: (fingerprint shortened a bit)

<link rel="stylesheet" href="/css/style.min.29f2f8a.css" integrity="sha256-KUxNL4o="/>

In the *.php output they render as:

<link rel="stylesheet" href="__h_pp_l1_1_RelPermalink__e=" integrity="__h_pp_l1_1_Data.Integrity__e="/>

Any hints where to look for the error?

Update:
When I remove | resources.PostProcess everything works fine.

Do I even even need PostProcess? I do not use server (because can’t see *.php output anyway), and use Tailwind, which I believe doing the CSS purging.