Developing Packages

Developers familiar with FreeBSD, pkg, and PHP, will find it fairly straightforward to create packages for pfSense® software. End users and organizations can benefit from developing a package that does not exist.

To submit a new package, create a pull request on GitHub for the pfSense software FreeBSD-Ports Repository so Netgate can evaluate the work for inclusion into the package repository for access by all users.

When developing packages, always target the latest development version of pfSense software first.

pfSense Software Package System

On pfSense software, every pfSense software package is also a FreeBSD port. These are installed and managed via pkg, even when using the GUI to add or remove packages. Binary packages from FreeBSD are added as dependencies of the pfSense software package, so they are installed automatically as well, along with any of their own required dependencies.

The basic idea is to make packages for pfSense software similar to FreeBSD packages, but with customization. One way this is achieved is by adding metadata about packages to the firewall configuration when it is installed, and also creating the configuration screen of an application using XML. pfSense software provides an optional framework to create the web interface and to store it in the XML configuration file of the firewall. The package writer is expected to convert the data from XML to the native format of the application.

Package System

pfSense software packages are typically composed of:

  • A Manifest File

  • Package configuration file(s)

  • Supporting files (.inc files, additional .php web interface files, etc.)

See also

See Package Port Directory Structure for a more in-depth list of files and their locations in the package structure.

Manifest File

The manifest file is located inside the package’s port directory:

<category>/pfSense-pkg-<package name>/files/usr/local/share/pfSense-pkg-<package name>/info.xml

For example:


This file contains basic information about the package. The format of the manifest XML file is as follows:

        <descr><![CDATA[Some cool program.]]></descr>


The version entry is updated automatically, use the template as shown.

Package Configuration Files

The manifest specifies a Package Configuration File for the package using the config_file tag. The convention is to keep this file inside the files/usr/local/pkg/ directory inside the port structure for the package.

The easiest way to get a feel for the format is to look at existing packages and how they use these configuration files, how their fields look, how the code behaves, and so on.

The format is:

<?xml version="1.0" encoding="utf-8" ?>
  <custom_php_global_functions><!-- PHP function call --></custom_php_global_functions>
  <custom_php_install_command><!-- PHP function call --></custom_php_install_command>
  <custom_php_pre_deinstall_command><!-- PHP function call --></custom_php_pre_deinstall_command>
  <custom_php_deinstall_command><!-- PHP function call --></custom_php_deinstall_command>
  <custom_add_php_command><!-- PHP function call --></custom_add_php_command>
  <custom_add_php_command_late><!-- PHP function call --></custom_add_php_command_late>
  <custom_delete_php_command><!-- PHP function call --></custom_delete_php_command>
  <custom_php_resync_config_command><!-- PHP function call --></custom_php_resync_config_command>
  <start_command><!-- PHP function call --></start_command>
  <custom_php_service_status_command><!-- PHP function call --></custom_php_service_status_command>
  <custom_php_validation_command><!-- PHP function call --></custom_php_validation_command>
  <custom_php_after_head_command><!-- PHP function call --></custom_php_after_head_command>
  <custom_php_command_before_form><!-- PHP function call --></custom_php_command_before_form>
  <custom_php_after_form_command><!-- PHP function call --></custom_php_after_form_command>

Field types


Combo/list box with interfaces list:

  <fielddescr>Interface Selection</fielddescr>
  <description>Select interfaces to listen on</description>
  <multiple/><!-- (optional) -->
  <size>10</size><!-- (optional) -->
  <hideinterfaceregex>(wan|loopback)</hideinterfaceregex><!-- (optional) -->
  <showvirtualips/><!-- (optional) -->
  <showips/><!-- (optional) -->
  <showlistenall/><!-- (optional) -->

Field with text description and a enable/disable checkbox:

  <description>Select this option to enable this config</description>

Single line text edit element

  <description>Enter package username</description>

Special input element for passwords, all input will be masked with * symbol on GUI but clear text on xml config file:

  <description>Enter password</description>

Multi-line text edit element:

  <fielddescr>Custom options</fielddescr>
  <description>Paste custom config here</description>
  <encoding>base64</encoding><!-- (optional) -->

Combo Box with drop-down list items:

  <fielddescr>Some Choice</fielddescr>
  <description><![CDATA[Select a choice]]></description>
    <option><name>Choice A</name><value>a</value></option>
    <option><name>Choice B</name><value>b</value></option>
  <multiple/><!-- (optional) -->
  <size>10</size><!-- (optional) -->

Information text without any options to select:

  <fielddescr>Additional info</fielddescr>
  <description>show info text on package GUI</description>

Additional buttons to take additional actions on packages:

 <fielddescr>Reload config</fielddescr>
 <description>click to force a config reload</description>
 <placeonbottom/><!-- Use this option to place the button besides save default button -->

In package .inc file, to check what button was selected, use:

if (($_POST['Submit'] == 'Save') {
   /* Do save stuff */
if (($_POST['Submit'] == 'Reload') ||
    !isset($_POST['Submit'])) {
   /* Do reload stuff */

Field groups


Several options can be combined into a single row as an option group using <combinefields>

For example this block groups three options onto a single row:

        <fielddescr>Option 1</fielddescr>
        <description>Option 1</description>
        <fielddescr>Option 2</fielddescr>
        <description>Option 2</description>
        <fielddescr>Option 3</fielddescr>
        <description>Option 3</description>

Used in pkg_edit.php to add multiple config lines like a table on package GUI. Inside rowhelper, add any field type described above:

<description><![CDATA['Format' - Choose the file format that url will retrieve or local file format.]]></description>
      <fielddescr>URL or local file</fielddescr>

Used with pkg.php to have multiple config of the same xml page. Useful to access lists, users lists, multi daemon configurations, etc:

    <fielddescr>Update Frequency</fielddescr>

Binaries from FreeBSD

The actual binaries are normal FreeBSD package binaries for that particular program. Once listed as a dependency for a pfSense package in its Makefile, the builders compile the dependencies automatically and copy them to the pfSense software package servers. There is no need to specify these in XML.

Updating Packages

When updating a package is it important to bump the version in its Makefile otherwise the package will not be rebuilt and made available to others.

Repository Branches

When submitting changes, they are typically submitted to the devel branch of the FreeBSD-Ports Repository. In order to show to all users, the changes must be placed in the current release branch as well, such as RELENG_2_7_2.

Ideally, the changes should be submitted to the development branches and tested on systems pulling packages from the development repository. Once the changes have been tested, they can be placed into the release branch for deployment to a wider audience.

Testing/Building Individual Packages

Archive files from pkg may be copied to the firewall and added with pkg directly. The good thing about using pkg is that the GUI packages and CLI packages are all the same. Files for pfSense software packages are all kept together inside the archive, dependencies such as FreeBSD packages are in separate archives.

The package may be compiled on a local FreeBSD 14.0-CURRENT@0c783a37d5d5 builder, then pkg delete the old version and then pkg add the new one or use any other pkg operations needed.

For example, a basic package like Cron is pfSense-pkg-Cron-0.3.3, so if a new copy is built and put on the firewall:

# pkg add /path/to/file/pfSense-pkg-Cron-0.3.3.txz

It will also work with pkg add and a URL to an http or https web server.

The process for making a package is:

  • Check out the FreeBSD-Ports Repository.

  • Locate the port directory

  • Make changes

  • Run make package:

    $ git clone pfSense-ports
    $ cd pfSense-ports/blah/pfSense-pkg-foo/
    [hack, hack, hack]
    $ make package  (might need sudo)
    $ scp work/pkg/pfSense-pkg-foo* root@myfirewall:.

And then on the firewall:

# pkg add pfSense-pkg-foo-<version>.txz

Poudriere could also be setup for a custom repository but in most cases that will be overkill.

There are additional considerations when adding files, like updating the plist, and crafting a new pfSense package from scratch may be tricky if there is no prior knowledge of how the FreeBSD ports tree works.