← Back to Home

Taco Docs

What is Taco?

Taco is a library for quickly creating WordPress custom post types and terms using code. Taco treats custom post types like MVC frameworks treat models.

How Difficult is Taco to Learn?

Are you comfortable writing basic PHP — functions, accessing arrays, manipulating strings, etc.? Then you should be fine.


Installation

Composer is the preferred method for installing Taco. Visit the Taco homepage for instructions on installing Taco via Composer.


Dependencies

For your own sanity and security, you’re hopefully running PHP >= 5.4 and WordPress >= 4.0. But if not, Taco works as far back as:

  • WordPress >= 3.3
  • PHP >= 5.3

Your First Taco

Let’s make a simple custom post type with Taco.

Each custom post type you create with Taco should be its own plugin. Using the taco_ prefix isn’t necessary, but is recommended.

First, a Screenshot

Below is the admin interface that Taco will automatically create when we define our Person post type. We have Title, Content, First Name, and Last Name fields.

Create your plugin file

wordpress/wp-content/plugins/taco_person/taco_person.php

<?php
/**
 * Plugin Name: taco_person
 */
class Person extends \Taco\Post
{
  public function getFields()
  {
    return array(
      'first_name'=>array('type'=>'text'),
      'last_name' =>array('type'=>'text'),
    );
  }
}

Line by Line

First, let’s look at the filename and path:

wordpress/wp-content/plugins/taco_person/taco_person.php

WordPress looks for plugins in the wp-content/plugins directory. From there, WordPress does a loop over all the subdirectories and attempts to find a corresponding filename. Hence, your plugin name (taco_person) should always have a corresponding file (taco_person.php).

WordPress parses the file looking for a comment at the top with at least a "Plugin Name:" defined. For convenience, it’s easiest to make your Plugin Name the same as the directory name.

/**
 * Plugin Name: taco_person
 */

The \Taco\Post class is autoloaded by Composer, and \Taco\Post does most of the heavy lifting for custom post types. It registers our post type, adds meta boxes to the admin UI, and provides a nice OOP interface for programmatically interacting with the post type.

class Person extends \Taco\Post
{
  ...
}

The getFields method is where we define which custom fields we want our post type to have. In this case, we want two fields: first and last name. Your getFields method should just return an array. For this simple case, we will only define text fields, but Taco supports many more HTML5 input types.

public function getFields()
{
  return array(
    'first_name'=>array('type'=>'text'),
    'last_name' =>array('type'=>'text'),
  );
}

Activate Your Plugin

After writing your plugin code, you will need to activate your plugin using the WordPress admin interface.

Add a Post

Once your plugin is activated, you will see the sidebar item that Taco adds. Click into Person and start adding posts!


Naming your plugin

Good taco_person/taco_person.php
Bad taco-person/taco-person.php

WordPress conventions are to use dashes instead of underscores when naming plugins, but Taco recommends using underscores. Using dashes can cause WordPress to load plugins in an unexpected order, even resulting in fatal errors or the dreaded white screen of death. Generally using dashes works out fine because plugins don’t usually rely on each other. But if you want one Taco plugin to extend another, you are safer using underscores.


Post Type Names

Post type values identified by the post_type column in the wp_posts table. The post_type values are automatically generated by Taco and use the following patterns:

  • All lower case
  • Words separated by dashes
  • Based on the PHP class name

Examples of this in action:

  • Person is post_type of person
  • PageBlock is post_type of page-block

Field Names

You are best off using underscores for your field keys in getFields method because that empowers you to more easily use Taco’s OOP syntax in your templates:

wordpress/wp-content/plugins/taco_person/taco_person.php

public function getFields()
{
  return array(
    'first_name'=>array('type'=>'text'),
  );
}

wordpress/wp-content/themes/your-theme/single-person.php

<?php
$person = Person::find($post->ID);
echo $person->first_name;
// $person->first-name; <!-- this would throw a PHP error

Adding Fields

Add fields to your Taco custom post type by defining your getFields method. You can use normal HTML5 attributes and types:

public function getFields()
{
  return array(
    'first_name'      =>array('type'=>'text'),
    'quote'           =>array('type'=>'textarea'),
    'email'           =>array('type'=>'email'),
    'favorite_color'  =>array('type'=>'color'),
    'is_professional' =>array('type'=>'checkbox'),
    'age'             =>array('type'=>'number'),
    'favorite_sauce'  =>array(
      'type'=>'select',
      'options'=>array(
        'mild'  =>'Mild',
        'medium'=>'Medium',
        'spicy' =>'Spicy'
      ),
    ),
    'photo_path'      =>array('type'=>'image'),
    'resume_pdf_path' =>array('type'=>'file'),
  );
}

Adding Fields

Add fields to your Taco custom post type by defining your getFields method. You can use normal HTML5 attributes and types:


WordPress Core Fields

WordPress offers lots of core fields that you can attach to your custom post type:

  • title — accessed with Taco via $taco_post->post_title
  • editor — accessed with Taco via $taco_post->post_content
  • author
  • thumbnail
  • excerpt
  • trackbacks
  • custom-fields
  • comments
  • revisions
  • page-attributes
  • post-formats

Taco sees this as overkill for most custom post types. So by default, Taco will only attach the title and editor fields to a Taco custom post type for use in the admin add/edit forms. To override this default and set your own core fields, add a getSupports method to your class:

public function getSupports()
{
  // will only attach the title field, not the editor
  return array('title');
}

Required Fields

To make a field required, just add a required attribute to your field definition in getFields:

public function getFields()
{
  return array(
    'first_name'=>array('type'=>'text', 'required'=>true),
  );
}

Labels

By default, labels are automatically generated using a humanized version of the field name. To override a field’s label, use the label attribute:

public function getFields()
{
  return array(
    'first_name'=>array(
      'type'=>'text',
      'label'=>'Your first name'
    ),
  );
}

Descriptions

To add a field description (will be rendered as italicized help text under the input field), set the description attribute when configuring your field in getFields:

public function getFields()
{
  return array(
    'first_name'=>array(
      'type'=>'text',
      'description'=>'The name you like to be called by friends',
    ),
  );
}

Validation

Unless overridden, validators will automatically run on each field according to these rules:

  • Is the field required?
  • Does the value entered exceed the maxlength attribute (if applicable)?
  • Is the selected option valid (for select elements)?

Custom validators can also be applied by overriding the isValid method:

public function getFields()
{
  return array(
    'date_of_birth'=>array('type'=>'date'),
  );
}

public function isValid($vals)
{
  // Let \Taco\Post::isValid do most of the work
  $parent_is_valid = parent::isValid($vals);
  if (!$parent_is_valid) return false;
  
  // Verify person is 18 yrs or older based on date_of_birth field
  $age_seconds = time() - strtotime($vals['date_of_birth']);
  $min_age_seconds = 18*60*60*24*365;
  $is_underage = $age_seconds < $min_age_seconds;
  if ($is_underage) {
    $this->_messages['date_of_birth'] = 'Too young';
    return false;
  }
  
  return true;
}

WYSIWYG Editors

Add the wysiwyg class to your field to get a WYSIWYG editor:

Note You must also have editor in your getSupports method. Unlike some plugins, Taco enables you to have multiple WYSIWYG fields on the same page.

public function getFields()
{
  return array(
    'quote'=>array('type'=>'textarea', 'class'=>'wysiwyg')
  );
}

public function getSupports()
{
  return array('title', 'editor');
}

Querying Posts

Taco uses core WordPress post and postmeta table storage. So you can always access custom post type data by calling WordPress functions like get_post, get_posts, etc.

But Taco also provides a clean object oriented interface to post data, both for retrieving and storing data.

Getting your custom post data out of the database is straightforward:

wordpress/wp-content/themes/your-theme/single-person.php

<?php
$person = Person::find($post->ID); // WordPress has populated $post
echo $person->first_name;

Methods for Getting Posts

Method Description Usage
find Find a single post by ID
$person = Person::find(5);
load Load a single post by ID
$person = new Person;
$person->load(5);
getBy Gets posts by one custom field key and value
$persons = Person::getBy('is_featured', 1);
getOneBy Get the first post by one custom field key and value
$person = Person::getOneBy('is_featured', 1);
getByTerm Gets posts by one matching term
// Get persons with term-slug checked
$persons = Person::getByTerm('my-taxonomy', 'term-slug');

// Get blog posts with category id 12 checked
$posts = Post::getByTerm('category', 12, 'id');
getOneByTerm Get the first post by one matching term
// Get first person with term-slug checked
$person = Person::getOneByTerm('my-taxonomy', 'term-slug');

// Get first blog post with category id 12 checked
$post = Post::getOneByTerm('category', 12, 'id');
getAll Get all the posts
$persons = Person::getAll();
getWhere Gets posts by get_posts criteria.
Note that if you're calling this with only one meta_key and meta_value, you may want to use getBy instead.
$persons = Person::getWhere(array(
  'meta_query'=>array(
    array(
      'key'  =>'is_featured',
      'value'=>true,
    ),
  ),
  'orderby' =>'post_date',
  'order'   =>'DESC'
));
getOneWhere Same as getWhere but only returns one record
$person = Person::getOneWhere(array(
  'meta_query'=>array(
    array(
      'key'  =>'is_featured',
      'value'=>true,
    )
  ),
  'orderby' =>'post_date',
  'order'   =>'DESC'
));
getPairs Gets pairs (id=>title) by get_posts criteria.
$pairs = Person::getPairs();
getPairsBy Gets pairs by key/value criteria.
$pairs = Person::getPairsBy('is_featured', 1);

Saving Posts

Using the WordPress admin UI, custom post types are automatically saved just like any normal WordPress post. There's no work to do.

But saving your post programmatically is also easy, which is great for things like data import scripts. First, set your data, then save. You can set your data multiple ways:

  • One by one assignment
  • Mass assignment
  • Assignment on instantiation

Assign attributes one by one

$testimonial = new Testimonial;
$testimonial->person = 'Abraham Lincoln';
$testimonial->quote = 'The thing about quotes on the internet is that you cannot confirm their validity.';
$testimonial->save();

Mass assignment of variables

$testimonial = new Testimonial;
$testimonial->assign(array(
  'person'=> 'Abraham Lincoln',
  'quote' => 'The thing about quotes on the internet is that you cannot confirm their validity.'
));
$testimonial->save();

Assignment on instantiation

$testimonial = new Testimonial(array(
  'person'=> 'Abraham Lincoln',
  'quote' => 'The thing about quotes on the internet is that you cannot confirm their validity.'
));
$testimonial->save();

Overriding the Save Method

Do you need custom save functionality? For instance, you may want to perform another action upon save:

  • Auto-save calculated/generated fields
  • Send an email upon save
  • Interact with a 3rd party API

Note You must include the $exclude_post=false parameter when overriding the save method to avoid a WordPress infinite recursion problem.

Example: Send an email upon save

// Send an email on save
public function save($exclude_post=false)
{
  $subject = sprintf('New post: %s', $this->get('post_title'));
  $body = $this->get('post_content');
  mail('test@example.com', $subject, $body);
  return parent::save($exclude_post);
}

Factory

Taco employs a factory pattern to create Taco post objects from WordPress post objects and IDs.

Single Post Factory

<?php
$person = \Taco\Post\Factory::create($post);
echo $person->first_name;

Multiple Post Factory

Often you have an array of WordPress post objects that you want converted into Taco posts:

<?php
$persons = \Taco\Post\Factory::createMultiple($posts);
echo current($persons)->first_name;

Templating Convenience Methods

Taco contains several convenience methods for common templating requirements:

Method Description Usage
getPermalink Returns the post permalink
echo $taco_post->getPermalink();
getTheTitle Returns the post_title run with the_title filter applied
echo $taco_post->getTheTitle();
getTheContent Returns the post_content run with the_content filter applied
echo $taco_post->getTheContent();
getTheExcerpt Returns the post_excerpt run with the_content filter applied. Shortens post_content if post_excerpt not defined.
echo $taco_post->getTheExcerpt();
getThePostThumbnail Returns the post_thumbnail image tag
echo $taco_post->getThePostThumbnail('full');
getPostAttachment Returns the post_thumbnail image array containing URL and size data
$image_array = $taco_post->getPostAttachment('full'); $image_width = $image_array['width'];
getPostAttachmentURL Returns the post_thumbnail image URL
echo $taco_post->getPostAttachmentURL('full');
getThe Returns any value run through the_content filter
echo $taco_post->getThe('custom_field_name');
getAnchorTag Returns the anchor tag
echo $taco_post->getAnchorTag();

Extending Core WordPress Post Types — Post and Page

You can extend built-in types like Post and Page using the same code as Custom Post Types. Let’s say your blog posts need to store a YouTube video ID:

wordpress/wp-content/plugins/taco_post/taco_post.php

<?php
/*
Plugin Name: taco_post
*/

class Post extends \Taco\Post
{
  public function getFields()
  {
    return array(
      'youtube_video_id' => array('type'=>'text'),
    );
  }
}

Exclude From Search

You can remove your post type from search results by overriding getExcludeFromSearch method:

public function getExcludeFromSearch()
{
  return true;
}

Sorting

Want results from getWhere, getAll to be sorted by a custom field? You got it dude. Just override getDefaultOrderBy.

// Simple example
public function getFields()
{
  return array(
    'first_name'=>array('type'=>'text'),
  );
}

public function getDefaultOrderBy()
{
  return 'first_name';
}

Numeric Sorting

If you have a numeric field, you probably want MySQL to sort your results numerically, not alphabetically (which is the default). Here's how:

// Numeric sorting by weight
// Notice type=number
public function getFields()
{
  return array(
    'weight'=>array(
      'type'=>'number',
      'description'=>'Lower weights bubble to the top'
    ),
  );
}

public function getDefaultOrderBy()
{
  return 'weight';
}

You can also change the default order:

public function getDefaultOrder()
{
  return 'DESC';
}

Rewriting URLS

This allows you to rewrite the URL for the archive pages of a custom post type. For example. If the custom post typ’s slug is 'product', you could change it to 'products'.

public function getRewrite()
{
  return array('slug'=>'products');
}

A full list of args that can be passed into the array can be found at:
http://codex.wordpress.org/Function_Reference/register_post_type#Arguments


Singular and Plural Labels

Singular and plural labels for your content type (e.g. Testimonial, Testimonials) are automatically assigned based on the class name (e.g. Testimonial). These values appear in the admin sidebar and toolbar. However, you can override these values using the getSingular and getPlural methods.

public function getSingular()
{
  return 'My Testimonial';
}

public function getPlural()
{
  return 'My Testimonials';
}

Admin Icon

By default, if you place a file named icon.png, icon.gif, or icon.jpg in your plugin directory, it will be automatically used. However, you can also override the getMenuIcon method to change the post type icon in the left sidebar.

public function getMenuIcon()
{
  return '/path/to/icon.png';
}

Index Columns

By default, any fields and taxonomies you add via getFields and getTaxonomies will automatically be part of the index page for your post type. Image file types will also render as thumbnails. You can optionally override the getAdminColumns method to change the columns visible in the admin index page.

public function getAdminColumns()
{
  return array('first_name');
}

Customize Meta Boxes

You can customize the meta box in two ways:

  • Customize the title
  • Customizing the body

By default, the meta box title will be the result of getSingular, but you can customize the title by overriding getMetaBoxTitle:

public function getMetaBoxTitle()
{
  return 'My Awesome Title';
}

By default, the meta box body will only contain a form. You can customize the meta box output with prepended out appended HTML by overriding the renderMetaBox method:

public function renderMetaBox($post, $post_config)
{
  $html = '<p>Prepended content</p>';
  
  // Form HTML
  // Pass true as the 3rd param for the parent method to
  // return HTML instead of echoing it.
  $html .= parent::renderMetaBox($post, $post_config, true);
  
  $html .= '<p>Appended content</p>';
  echo $html;
}

Customize a Meta Box Field Rendering

You can customize how a field renders by overriding the getRenderMetaBoxField method:

public function getFields()
{
  return array(
    'first_name'=>array('type'=>'text')
  );
}

public function getRenderMetaBoxField($name, $field) {
  if ($name === 'first_name') {
    return 'insert your html here for the first name field';
  }
  
  return parent::getRenderMetaBoxField($name, $field);
}

Multiple Meta Boxes

By default, any fields identified in getFields will be rendered inside of a single meta box. If you need multiple meta boxes, override the getMetaBoxes function. There are 3 ways to do this:

  • Automatic Prefix Grouping: Useful all fields have semantic grouping prefixes home_subtitle, home_copy, etc.
  • Semiautomatic Prefix Grouping: Useful for combining prefixed and non-prefixed fields
  • Manual Grouping: Internally, Custom converts Automatic and Semiautomatic Grouping to this syntax

Automatic Prefix Grouping

The code below will produce two metaboxes: Home and Footer

// field definitions
public function getFields()
{
  return array(
    'home_subtitle' => array('type'=>'text'),
    'home_copy' => array('type'=>'text'),
    'footer_copyright' => array('type'=>'text'),
    'footer_about_copy' => array('type'=>'texarea'),
  );
}

// Automatic Prefix Grouping
public function getMetaBoxes()
{
  return self::METABOX_GROUPING_PREFIX;
}

Semiautomatic Prefix Grouping

The code below will also produce two metaboxes: Home and Footer. But this time we've added another field to Footer. Note the * wildcard syntax.

// field definitions
public function getFields()
{
  return array(
    'home_subtitle' => array('type'=>'text'),
    'home_copy' => array('type'=>'text'),
    'footer_copyright' => array('type'=>'text'),
    'footer_about_copy' => array('type'=>'texarea'),
    'site_owner' => array('type', 'text'),
  );
}

// Semiautomatic Prefix Grouping
public function getMetaBoxes()
{
  return array(
    'home' => 'home_*', // Equivalent to array('home_*')
    'footer' => array('footer_*', 'site_owner'),
  );
}

Manual Grouping

You can always be verbose about your meta box groupings:

// field definitions
public function getFields()
{
  return array(
    'first_name' => array('type'=>'text'),
    'last_name' => array('type'=>'text'),
    'email' => array('type'=>'email'),
    'phone' => array('type'=>'text'),
  );
}

// simple example of multiple metaboxes
public function getMetaBoxes()
{
  return array(
    'name' => array('first_name', 'last_name'),
    'contact' => array('email', 'phone'),
  );
}

If you need more granular control over your generated meta box, for instance you need to customize the meta box title, override getMetaBoxes in this format:

// advanced example of multiple metaboxes
public function getMetaBoxes()
{
  return array(
    'name' => array(
      'title' => 'My Name Meta Box',
      'fields'=> array('first_name', 'last_name')
    ),
    'contact' => array(
      'title' => 'My Contact Meta Box',
      'fields'=> array('email', 'phone')
    )
  );
}

Taco vs. Advanced Custom Fields

Many WordPress developers use Advanced Custom Fields (ACF) to create custom post types. Taco and ACF do very similar things, but take different approaches:

Taco Advanced Custom Fields
Installation
Composer autoloader
or WordPress plugin
Installation
WordPress plugin
Register custom post types with
Done for you when extending \Taco\Post
Register custom post types with
Call register_post_type in functions.php
Add custom fields with
PHP code
Add custom fields with
WYSIWYG
PHP Style
Object Oriented
PHP Style
Procedural
Simple Template Usage
<?php
$event = Event::find($post->ID);
?>
<p>
<?php echo $event->city; ?>
</p>

Simple Template Usage
<?php while(have_posts()): ?>
<?php the_post(); ?>
<p>
<?php the_field('city'); ?>
</p>
<?php endwhile; ?>
Simple Querying
<?php
$posts = Event::getBy('city', 'NYC');





Simple Querying
<?php
$posts = get_posts(array(
'numberposts' => -1,
'post_type' => 'event',
'meta_key' => 'city',
'meta_value' => 'NYC'
));
Saving Posts
Done via WYSIWYG or code:
<?php
$event = new Event;
$event->city = 'Boulder';
$event->save();
Saving Posts
Done via WYSIWYG

Advanced Custom Fields takes a Drupal-esque approach to custom field — you create and manage them via WYSIWYG within the CMS.

Taco prefers to manage data structures in code written by a developer because that makes debugging easier, plus changes can run through version control and normal deployment procedures. But writing that code should be simple and intuitive, so Taco does most of the heavy lifting.


License

Taco is open source software released under the MIT License. You can View Taco on GitHub to view the license information and to contribute to the project.