Apostrophe 1.5: legacy documentation

Still using Apostrophe 1.5? Check out Apostrophe 2 for your future projects.

Designers Guide

Up to Overview

Designer's Guide

This section explains how to go about customizing the appearance and behavior of the site without writing new code (apart from simple calls to insert slots and otherwise use PHP as a templating language). It is intended to be read by front end designers (also known as front end developers).

Some familiarity with Symfony is expected. You will need to edit files like apps/frontend/config/app.yml to adjust settings, and you will be creating .php files although full PHP programming skills are not required.

Title Prefix

By default, the title element of each page will contain the title of that page. In many cases you'll wish to specify a prefix for the title as well.

You can do so by setting app_a_title_prefix in app.yml. This option supports optional internationalization:

        # You can do it this way...
          en: 'Our Company : '
          fr: 'French Prefix : '
        # OR this way for a single-culture site
        title_prefix: 'Our Company'      

Note that all changes to app.yml should be followed by a symfony cc command:

    ./symfony cc

Creating and Managing Page Templates and Layouts

Where do slots appear in a page? And how do you insert them?

Slots can be inserted in two places: in your site's <tt>layout.php</tt> file, which decorates all pages, and in page template files, which can be assigned to individual pages.

How to Customize the Layout

By default, the CMS will use the <tt>layout.php</tt> file bundled with it. If you wish, you can turn this off via app.yml:

        use_bundled_layout: false

CMS pages will then use your application's default layout. One strategy is to copy our <tt>layout.php</tt> to your application's template folder and customize it there after turning off use_bundled_layout.

How to Customize the Page Templates

The layout is a good place for global elements that should appear on every page. But elements specific to certain types of pages are better kept in page templates. These are standard Symfony template files with a special naming convention.

Page template files live in the templates folder of the a module. We provide these templates "out of the box:"

  • homeTemplate.php
  • defaultTemplate.php

homeTemplate.php is used by our default home page, and defaultTemplate.php is the default template if no other template is chosen.

You can change the template used by a page by using the template dropdown in the breadcrumb trail. This does not delete the slots used by the previous template, so you can switch back without losing your work.

How do you create your own template files? Don't alter the templates folder of the plugin. As always with Symfony modules, you should instead create your own a/templates folder within your application's modules folder:

    mkdir -p apps/frontend/modules/a/templates 

Now you can copy homeTemplate.php and defaultTemplate.php to this folder, or just start over from scratch. You can also copy _login.php if you don't like the way we present the login and logout options. We do not recommend altering the rest of the templates unless you have a clear understanding of their purpose and function and are willing to make ongoing changes when new releases are made. In general, if you can use CSS to match the behavior of our HTML to your needs, that will be more forwards-compatible with new releases of the CMS.

If you add additional template files, you'll need to adjust the app_a_templates setting in app.yml so that your new templates also appear in the dropdown menu:

            Home Page
            Default Page
            My Template

Custom Navigation in Templates and Layouts

Apostrophe supports three types of navigation components. These can be found in the aNavigation module. Including these navigation elements is as easy as including a component in your template or in layout.php. The three types of navigation elements are:

  • An accordion tree
  • A tabbed or vertical menu of links to child pages
  • A breadcrumb trail from the home page to the current page.

The navigation element, once used, will no longer require any additional SQL queries no matter how many similar navigation elements are included in a template or layout.

Accordion Navigation

Accordion-style navigation can be easily included in your template by doing the following. Accordion navigation is a compromise between showing the entire page tree and showing just the breadcrumb. Accordion navigation includes everything that would be included in a breadcrumb trail, plus the peers of all of those pages. This makes it easy to navigate to related pages at any level without being overwhelmed by a comprehensive list of all pages on the site. These links are rendered as nested ul elements.

    <?php include_component('aNavigation', 'accordion', array('root' => '/', 'active' => $page->slug, 'name' => 'normal')) ?>


  • root (required) - This is the slug of the root page that the menu should start from. Often the home page
  • active (required) - The page to build the navigation down to, will also recieve a css class of current
  • name (required) - The unique name of the navigation element for your template. This influences CSS IDs and can be used for styling

Tabbed Navigation

The tabbed navigation provides a navigation element that displays all of the children of a page. Note that depending on your CSS you can render it as a vertical menu. For an example, see the "subnav" area of the asandbox project; see `a/templates/_subnav.php).

    <?php include_component('aNavigation', 'tabs', array('root' => '/', 'active' => $page->slug, 'name' => 'tabs')) ?>


  • root (required) - This is the parent slug of the tabs that are displayed.
  • active (required) - This is the page that if in the navigation will recieve a current css class of current
  • name (required) - The unique name of the navigation element for your template
  • extras (optional) - Additional links to present at the beginning of the list

You can also inject additional links at the beginning of the list:

    <?php include_component('aNavigation', 'tabs', array('root' => '/', 'active' => $page->slug, 'name' => 'tabs', 'extras' => array('/page/slug/or/absolute/url' => 'Label'))) ?>

If you wish to link to a Symfony action on the site as an additional tab, you'll need to use url_for to create an absolute URL to it:

'extras' => array(url_for('module/action?my=parameter', true) => 'Label')
    <?php include_component('aNavigation', 'breadcrumb', array('root' => '/', 'active' => $page->slug, 'name' => 'bread')) ?>


  • root (required) - This is the root slug of the page the breadcrumb should begin with. Usually the home page
  • active (required) - This is the last element that the navigation should descend to. Usually the current page
  • separator (optional) - The separator to use between items, defaults to " > "
  • name (required) - the unique name of the navigation element for your template.

Inserting Slots in Layouts and Templates

Of course, creating layouts and templates does you little good if you can't insert user-edited content into them. This is where the CMS slot helpers come in.

Here's how to insert a slot into a layout or page template:

    <?php # Once at the top of the file ?>
    <?php use_helper('a') ?>

    <?php # Anywhere you want a particular slot ?>
    <?php a_slot('body', 'aRichText') ?>

Notice that two arguments are passed to the <tt>a_slot</tt> helper. The first argument is the name of the slot, which distinguishes it from other slots on the same page. *Slot names should contain only characters that are allowed in HTML ID and NAME attributes*. We recommend that you use only letters, digits, underscores and dashes in slot names. The slot name will never be seen by the user. It is a useful label such as body or sidebar or subtitle.

The second argument is the type of the slot. "Out of the box," apostrophePlugin offers a useful array of slot types:

  • aText (plaintext)
  • aRichText (allows WYSIWYG formatting)
  • aFeed (brings any RSS or Atom feed into the page)
  • aImage (still images from the media repository)
  • aSlideshow (a series of images from the media repository)
  • aButton (an image from the media repository, and an editor-configurable link)
  • aVideo (video from YouTube and other providers)
  • aFile (PDF files, Word documents, Excel documents, etc. from the media repository)
  • aRawHTML (Unfiltered HTML code)

The use of these slots is largely self-explanatory and we encourage you to play with the demo and try them out.

If you are starting from our sandbox project you also have the blog plugin, which adds aBlog and aEvent slots.

You can add additional slot types of your own and release and distribute them as plugins as explained in ManualDevelopersGuide.

aText Slots

aText slots contain only plaintext. URLs are automatically rendered as links, email addresses are rendered as obfuscated mailto: links, and newlines are rendered as line breaks.

Note that the special slot name title is reserved for the title of the page and is always of the type aText. While you don't really need to provide an additional editing interface for the title, you might also want to insert it elsewhere in your page layout or template as a design element:

    <?php a_slot('title', 'aText') ?>

The behavior of most slot types can be influenced by passing options to them from the template or layout. You do this by passing an array of options as a third argument to the helper, like this:

    <?php a_slot('aboutus', 'aText', array('multiline' => true)) ?>

The multiline option specifies that a plaintext slot should permit multiple-line text input.

aRichText Slots

Here is a simple example of a rich text slot:

    <?php a_slot('ourproducts', 'aRichText') ?>

The rich text slot allows users to edit content using a rich text editor. The HTML they enter is filtered for correctness and to ensure it does not damage your design. Read on for more information about ways to adjust these filters.

The tool option is one of the most useful options for use with rich text slots:

    <?php a_slot('subtitle', 'aRichText',
      array('tool' => 'basic')) ?>

Here we create a subtitle rich text slot on the page which is editable, but only with the limited palette of options provided in FCK's basic toolbar (bold, italic and links).

Note that you can create your own custom toolbars for the FCK rich text editor. Add these to web/js/fckextraconfig.js (at the project level, not in the plugin) and they will be found automatically. Here is an example:

    FCKConfig.ToolbarSets["Sidebar"] = [
    ] ;

For more complete examples of what can be included here, see apostrophePlugin/web/js/fckeditor/fckconfig.js. (You do not need to, and should not, duplicate this entire file in fckextraconfig.js. Just add and override things as needed.)

Other notable options to aRichText slots include:

  • allowed-tags is a list of HTML elements to be permitted inside the rich text. By default Apostrophe filters out most HTML elements to prevent pasted content from Microsoft Word and the like from wrecking page layouts. You can pass an array of HTML element names (without angle brackets), or a string like that accepted by the PHP strip_tags function.

This allows us to write a better version of the subtitle slot above that filters the HTML to make sure it's suitable to appear in a particular context:

<?php a_slot('subtitle', 'aRichText',

array('tool' => 'basic', 'allowed-tags' => '<b><i><strong><em><a>')) ?>

Note that we list both the b tag and the strong tag here. We do this because different browsers submit slightly different rich text.

The default list of allowed tags is:

h3, h4, h5, h6, blockquote, p, a, ul, ol, nl, li, b, i, strong, em, strike, code, hr, br, div, table, thead, caption, tbody, tr, th, td, pre

  • allowed-attributes is a list of HTML attributes to be accepted, on a per-element basis. Unlike strip_tags Apostrophe's robust, high-performance HTML filter, aHtml::simplify, removes all inappropriate HTML attributes. Here is the default list of allowed attributes:
      "a" => array("href", "name", "target"),
      "img" => array("src")
  • allowed-styles is a list of CSS style names to be permitted, again on a per-element basis. By default, no styles are permitted in rich text editor content. You can alter this to allow more creative table styling, at the cost that a truly persistent user might manage to wreck the page layout. The format is the same as that used above for allowed attributes.

If you want to change the default settings for allowed tags, attributes and styles, you may find it more convenient to use app.yml for this purpose (default values are shown, be sure to keep them unless you specifically want to disable things that are normally considered essential):

    allowed_tags: [h3, h4, h5, h6, blockquote, p, a, ul, ol, nl, li, b, i, strong, em, strike, code, hr, br, div, table, thead, caption, tbody, tr, th, td, pre]
      a: [ href, name, target ]
      img: [ src ]
    allowed_styles: ~

aFeed Slots

The aFeed slot allows editors to insert an RSS feed into the page:

    <?php a_slot('subtitle', 'aFeed') ?>

When the user clicks "Edit," they are invited to paste an RSS feed URL. When they click "Save," the five most recent posts in that feed appear. The title of each feed post links to the original article on the site of origin as specified by the feed.

You can change this behavior with the following options. The defaults are shown as examples:

  • 'links' => true determines whether links to the original posts are provided.
  • 'dateFormat' => 'Y m d' lets you specify your own date and time format (see the PHP date() function). 'Y m d' is not actually our default, as our default is more subtle than can be output with the date function alone (we include the year only when necessary and so on). Our default is very nice, but very American, so feel free to override it.
  • 'markup' => '<strong><em><p><br><ul><li>' changes the list of HTML elements we let through in markup. We apply our usual aHtml::simplify() method, which is smarter than PHP's strip_tags alone.
  • 'interval' => 300 specifies how long we hold on to a feed before we fetch it again. This is important to avoid slowing down your site or getting banned by feed hosts. We rock Symfony's caching classes to implement this. Check out aFeed::getCachedFeed if you're into that sort of thing.
  • 'posts' => 5 determines how many posts are shown.

aSlideshow Slots

Slideshow slots allow editors to select one or more images and display them as a slideshow. The exact behavior of the slideshow can be controlled by various options.

Here is a simple example:

    <?php a_slot('travel', 'aSlideshow') ?>

By default users can select any images they wish and add them to the slideshow, and the slideshow will advance when they click on an image. Left and right arrows to move back and forward in the slideshow appear above the image unless turned off by the arrows option.

Options are available to control many aspects of slideshow slots:

  • 'width' => 440 determines the width of the image, in pixels. Images are never rendered larger than actual size as upsampling always looks awful.
  • 'height' => 330 determines the height of the image, in pixels. It is ignored if `flexHeight' => true is also present.
  • 'resizeType' => 's' determines the scaling and cropping style. The s style scales the image down if needed but never changes the aspect ratio, so the image is surrounded with white bars if necessary (see flexHeight for a way to avoid this). The c style crops the largest part of the center of the image that matches the requested aspect ratio.
  • 'flexHeight' => false determines whether the height of the image is scaled along with the requested width to maintain the aspect ratio of the original. Set this option to true to scale the height.
  • 'maxHeight' => 500 used in conjunction with flexHeight, sets a maximum height that the image can be. Useful if you have a slideshow with a mixture of vertical and horizontal images.
  • 'limit' => 10 limits the number of displayed images to 10 even if there are more in the slideshow.
  • 'ui' => false shuts off the arrow buttons and does not enable any javascript for the slideshow. Useful if you are displaying a thumbnail excerpt of the slideshow or want to implement other alternate UI. Often used together with limit.
  • 'firstOnly' => true is equivalent to setting both 'limit' => 1 and 'ui' => false`.
  • 'title' => false specifies whether the title associated with the image in the media repository should be shown on the page. CSS can be used to display this title in a variety of interesting ways.
  • 'description' => false specifies whether the rich text description associated with the image in the media repository should be shown on the page.
  • 'link' => false specifies a URL that the image should link to when clicked upon. Note that in most cases you probably want to use an aButton slot for this sort of thing.

These options suffice for most purposes. There is one more option, constraints, which takes an array of constraints that can be used to limit which images the user can select from the media repository. By default no constraints are applied. The possible constraints are:

  • 'aspect-width' and 'aspect-height' specify an aspect ratio. The media browser will show only media that match this aspect ratio. Both must be specified. For instance:
    'constraints' => array('aspect-width' => 4, 'aspect-height' => 3)
  • 'minimum-width' and 'minimum-height' specify minimum width and height in pixels. Only media meeting these minimum criteria will be shown. You are not required to use both options although it usually makes sense to specify both.
  • 'width' and 'height' specify that only images of that exact width and/or height should be selectable for this slideshow slot. It is not mandatory to specify both, and if you are using flexHeight you might not want to specify a height constraint.

Here are three examples:

This slot will render the image 400 pixels wide. The original must be at least 400 pixels wide. The height will scale along with the width.

    <?php a_slot('landscape', 'aImage', array('width' => 400, 'flexHeight' => true, 'constraints' => array('minimum-width' => 400))) ?>

This slot will be render images at 640x480 pixels. The original must be at least 640 pixels wide. The aspect ratio must be 4x3.

    <?php a_slot('landscape', 'aImage', array('width' => 640, 'height' => 480, 'constraints' => array('minimum-width' => 640, 'aspect-width' => 4, 'aspect-height' => 3))) ?>

This slot will crop the largest portion of the center of the selected image with a 400x200 aspect ratio and scale it to 400x200 pixels. The selected image must be at least 400 pixels wide.

    <?php a_slot('landscape', 'aImage', array('width' => 400, 'height' => 200, 'resizeType' => 'c', 'constraints' => array('minimum-width' => 400))) ?>

Slideshows with images of varying dimensions can be confusing to look at unless the aspect ratio is the same. Fortunately slideshows support all of the sizing, cropping and constraint options that are available for the aImage slot (see above). We recommend either using resizeType => 'c' to crop to a consistent size (and therefore aspect ratio) or using the aspect-width and aspect-height parameters to the constraints option to ensure a consistent aspect ratio.

Other Options include:

  • 'random' => false determines whether the slideshow is shown in the order selected by the editor or in a random order. Set this option to true for a random order.
  • 'arrows' => true indicates that arrows to move forward and backward in the slideshow should appear above the image. By default they do appear. You can turn this off by passing false.
  • 'interval' => false sets the interval for automatic advance to the next image in the slideshow. By default the slideshow does not advance automatically. For automatic advance every ten seconds pass 'interval' => 10. Slideshows automatically repeat once they have advanced to the final image. If the user clicks on the slideshow, auto-advance stops, and the user can navigate manually by clicking to advance or using the arrows if present.
  • 'credit' => false determines whether the credit field from the media repository is shown with each image.
  • 'itemTemplate' => slideshowItem allows you to specify a different slideshow item template at the project level. The default template is bundled with the slot and is named _slideshowItem.php.

aButton Slots

Button slots are similar to image slots. However, they also allow the editor to specify a target URL and a plaintext title. When they click on the button, they are taken to the URL.

Here is an example:

    <?php a_slot('logo', 'aButton') ?>

Button slots support all of the options supported by the aImage slot (see above). In particular you might want to specify a default image.

If the title and/or description options are turned on, they are rendered as links to the button's destination URL.

aVideo Slots

The aVideo slot allows video to be embedded in a page.

Apostrophe's media repository does not store video directly. Instead Apostrophe manages videos hosted on external services such as YouTube, Viddler and Vimeo. YouTube is tightly integrated into Apostrophe, so editors can search YouTube directly from the repository in order to easily add videos. Embed codes for other video services can also be added to the media repository. Either way, Apostrophe wrangles the necessary embed codes and ensures that they render at the desired size.

Here is an example:

    <?php a_slot('screencast', 'aVideo') ?>

Note that you can disable support for embed codes from non-YouTube services if you wish:

    embed_codes: false

Note that the plugin itself ships with this feature disabled by default, but the app.yml file of our sandbox project does turn it on.

The video slot supports all of the options supported by the image slot (described above), with the following differences and exceptions:

  • The width option defaults to 320 pixels.
  • The height option defaults to 240 pixels.
  • The resizeType option is ignored. As a general rule video hosting services do not permit cropping and choose their own approaches to letterboxing. We recommend using the flexHeight option if there is any doubt about the dimensions of the video.
  • The constraints option is supported. The constraints will be applied based on the dimensions of the original video (if from YouTube). For videos from other hosting services (pasted as embed codes in the media repository) constraints are based on the dimensions of the thumbnail manually uploaded by the editor.
  • The link and defaultImage options are not supported.

aFile Slots

The aFile slot allows download links to office documents, such as PDFs and Word documents, to be embedded in the page. A clickable icon representing the format is presented as a way of downloading the document.

The title and description options are supported. By default, both the title and the description of the file from the media repository are displayed. You can turn off either or both by setting these options to false.

The aFile slot selects a media item of the type office. The list of file formats that are acceptable as office documents can be reconfigured by customizing the app_aMedia_types app.yml setting. Currently you must override this entire option if you wish to change the settings for any type. We recommend sticking with our standard list of allowable file formats. But if you wish to experiment see BaseaMediaTools.class.php for the default value, which you must reproduce and edit in your app.yml file. Note that overriding these settings does not mean that Apostrophe becomes able to render new image or multimedia formats natively. At a later time we may provide a more convenient way to override or extend settings for just one type (such as office).

(Note that the aPdf slot is now considered a legacy slot and not enabled by default. Although rendering of thumbnails was an attractive feature ghostscript just isn't reliable enough and rejects many valid PDFs, which is not acceptable.)

aRawHTML Slots: When You Must Have Raw HTML

Honestly, we're not big fans of raw HTML slots. Editors tend to paste code that breaks page layouts on a fairly regular basis. That's why our rich text slots filter it so carefully.

However there are times when you must have the embed code for a mailing list signup form service or similar third-party website feature.

In these situations the aRawHTML slot is useful.

Here is an example:

    <?php a_slot('mailinglist', 'aRawHTML') ?>

This slot displays a multiline text entry form where the editor can paste in HTML code directly. This code is not validated in any way, so you should think carefully before offering this feature to less experienced editors.

There is a way to recover if you paste bad HTML that breaks the page layout. Visit the page with the following appended to the URL:


When this parameter is present in the URL, raw HTML slots will escape their contents so that you see the source code, safely defanged. You can then click "Edit" and make corrections as needed.

That's it for the standard slots included with Apostrophe! Of course there will be more. And you can add more yourself. See the developer's section of this document.

Changing Button Labels and Media Browser Prompts

Beginning in version 1.5 It is possible to change the labels of the buttons in all of our standard slots. To change the label of the Edit button, set the editLabel option as you see fit. If the slot manipulates media and has a "Choose Image" or similar button, you can change the label of this button by setting the chooseLabel option.

The defaults for these options are reasonable and should not need to be changed in most cases. Occasionally you may wish to more clearly distinguish a standalone slot's function from that of surrounding content areas.

Slot Variants: More Mileage From Your Templates

It's possible to specify different options to each slot in different page templates. And for many designs this is the way to go.

But sometimes you'd like to have a more flexible setup in which the same template or layout can serve the needs of more pages.

One solution would be to let users specify slot options or even CSS classes directly, but this has a tendency to be confusing. So we've provided a better solution: slot variants.

If you define variants like this in app.yml:

          label: Normal
            interval: 0
            title: false
            arrows: true
          label: Compact
            interval: 0
            title: true
            arrows: true
            itemTemplate: slideshowItemCompact
          label: Auto Play
            interval: 4
            title: true
            arrows: false         
            itemTemplate: slideshowItemCompact                     

And you have allowed these variants, either by not specifying the app_a_allowed_slot_variants option at all (note that it is set but empty in our sandbox project) or by setting it up as follows:

          - normal
          - compact
          - autoplay    

Then the user will be presented with a choice of these three variations on the slideshow slot in an "Options" menu to the right of the "Choose Images" button, provided that we have actually allowed those variants.

How does this work? Each variant has a label and an optional set of options. When the user switches between variants, the slot is refreshed with the new options in effect.

Sometimes you will want to define variants on a slot that are only suitable for use in a particular place. For instance, you wouldn't want to allow an "extra-wide" image slot in a sidebar. Beginning in version 1.04, you can address this by passing the allowed_variants option when inserting an area or slot:

<?php a_slot('aImage', array('allowed_variants' => array('narrow'))) ?>

Note that this means that you can lock a slot down to a single variant that would not otherwise be the default. If you allow only one variant, the options menu does not appear, since there are no other choices.

You can do the same thing for an area by adding allowed_variants to the type_options for the slot type in question.

Prior to version 1.4.1/svn commit 1569 the app_a_allowed_slot_variants option did not exist, so it was necessary to specifically allow only the appropriate variants in every a_slot or a_area call if any of the variants were template-specific and a poor choice for general use. This new option makes it easy to set up a more restrictive list of variants that are allowed when allowed_variants is not specified at the slot level.

In addition to changing options, variants also set a CSS class on the slot's outermost container. Specifically, when the compact option is in use, the CSS class compact will be set on that slot's outermost container. This is very useful for styling purposes.

Prior to version 1.04 slot variant options must fully contradict each other for consistent results. That is, if you set the interval option for one variant, you must set a value for it for every variant (even if that value is false). This requirement is removed in version 1.04.

What about newly added slots? If there are variants for a slot, the first variant in the list is used for new slots of that type (in areas) or for slots whose variant has never been set (for standalone slots). If an allowed_variants option is present for a particular slot or area, or there is a global app_a_allowed_slot_variants option, then the first allowed variant is used.

Inserting Areas: Unlimited Slots in a Vertical Column

Slots are great on their own. But when you want to mix paragraphs of text with elements inserted by custom slots, it is necessary to create a separate template file for every page. This is tedious and requires the involvement of an HTML-savvy person on a regular basis.

Fortunately apostrophePlugin also offers "areas." An area is a continuous vertical column containing multiple slots which can be managed on the fly without the need for template changes.

You insert an area by calling a_area($name) rather than a_slot($name):

    <?php a_area("sidebar") ?>

When you insert an area you are presented with a slightly different editing interface. At first there are no editable slots in the area. Click "Insert Slot" to add the first one. You can now edit that first slot and save it.

Add more slots and you'll find that you are also able to delete them and reorder them at will.

By default new slots appear at the top of an area. If you don't like this, you can change it for your entire site via app.yml:

        new_slots_top: false

An area has just one version control button for the entire area. This is because creating, deleting, and reordering slots are themselves actions that can be undone through version control.

In a project with many custom slot types, you may find it is inappropriate to use certain slot types in certain areas. You can specify a list of allowed slot types like this:

    <?php a_area("sidebar",
      array("allowed_types" => array("aText", "myCustomType"))) ?>

Notice that the second argument to a_area is an associative array of options.

  • allowed_types option allows us to specify a list of slot types that are allowed in this particular area.
  • delete [ true | false ] option allows you to disable deleting of slots in an a_area() or enable reseting of a singleton slot in a_slot(...).
  • history [ true | false ] option allows you to toggle the history functionality off and on for the slot.
  • arrows [ true | false ] option allows you to toggle the re-organize arrows off for areas.
  • areaLabel [ 'string' | null ] option to change the label of the add content button from "Add Content" to something different, as we do for the heading area of the blog and news pages.
  • areaHideWhenEmpty [ true | false ] option allows you to prevent the area from outputting empty divs on the page if the user has no editing privileges and the area contains no content.

In addition, you can pass options to the slots of each type, much as you would when inserting a single slot:

      array("allowed_types" => array("aText", "myCustomType"),
        "type_options" => array(
          "aText" => array("multiline" => 1))));

Here the multiline option specifies that all aText slots in the area should have the multiline option set.

Easy Shortcut: The a/standardArea Component

Much of the time you simply want to let users add as many of each of the standard slots as they wish. There is a convenient component for achieving this:

<?php include_component('a', 'standardArea', array('name' => 'body', 'width' => 480, 'toolbar' => 'Main')) ?>

This component calls a_area for you with a standard list of slots including rich text, slideshows, smart slideshows, video, blog, event feed slots. And you can still specify the area name, the width for media slots, and the FCK or CK toolbar to be used. You can also specify the height and flexHeight options.

Of course in some cases you might want an almost standard list of slot choices. In such cases you can use plusSlots to add a few choices or minusSlots to subtract a few. Typically you won't need plusSlots unless you have added custom slot types in your project. We use minusSlots to prevent blog posts and events from referring to each other in an infinite loop.

<?php include_component('a', 'standardArea', array('name' => 'blog-body', 'width' => 480, 'toolbar' => 'Main', 'minusSlots' => array('aEvent', 'aBlog'), 'areaOptions' => array('areaHideWhenEmpty' => true))) ?>

You can specify an exact list of slots to be allowed with the slots option, and you can pass options intended for specific slots with the slotOptions option, which takes an associative array:

<?php include_component('a', 'standardArea', array('name' => 'blog-body', 'width' => 480, 'slotOptions' => array('aRichText' => array('allowed-tags' => '<h3><h4><p>')))) ?>

You can also pass options to the area itself via the areaOptions key as shown above.

It is possible to override the standard list of slots, the standard options for those slots, and the behavior of the area itself via app.yml.

To specify options for the area itself, such as areaLabel:

      # Shorten the "Add Content" button
      areaLabel: "Add"

To change the list of slots that are permitted to a very short list:

      - aRichText
      - aSlideshow

To specify all of the options for all of the slots (note that this means you won't automatically get any nice defaults added for new slots in the plugin):

        title: true
      ... etc., all details for ALL of the slots

To simply override some settings for various slots while keeping the helpful defaults for the rest, which we recommend:

      # Change this one thing, keeping everything else the same
        title: true

You can still override aTools::standardAreaSlots and aTools::standardAreaOptions() at the project level if you prefer.

Global Slots and Virtual Pages

Most of the time, you want the content of a slot to be specific to a page. After all, if the content was the same on every page, you wouldn't need more than one page.

However, it is sometimes useful to have editable content that appears on more than one page. For instance, an editable page footer or page subtitle might be consistent throughout the site, or at least throughout a portion of the site.

The quickest way to do this is by adding a "global slot" to your page template or layout. Just set the global option to true when inserting the slot:

    <?php a_slot('footer', 'aRichText',
      array('tool' => 'basic', 'global' => true)) ?>

The content of the resulting slot is shared by all pages that include it with the global option.

Note that you can use the global flag with areas as well as slots:

    <?php a_area('footer', array(
      'allowed_types' => array('aRichText', 'aImage'),
      'global' => true
      )) ?>

By default, global slots can be edited only by users with editing privileges throughout the site. Otherwise users with control only over a subpage could edit a footer displayed on all pages. See below for more information about how to override this rule where appropriate.

Virtual Pages: When Global Slots Are Not Enough

Global slots will do the job for most situations that front end developers will encounter. If you're not a PHP developer looking to use slots and areas to manage dynamic content related to your own code, it's probably safe to skip this section, although it is sometimes useful to apply these techniques if you would otherwise have hundreds of global slots in your design.

Conceptually, all global slots reside together on a virtual page with the slug global. Since there is no leading /, this page can never be navigated to. The global virtual page resides outside of the site's organizational tree and is used only as a storehouse of shared content.

For headers and footers, this works very well. But if you have a larger amount of shared content, the model begins to break down. Fortunately there's a solution: group your content into separate virtual pages.

When you include a slot this way:

    <?php a_slot('biography', 'aRichText', array('tool' => 'basic', 'slug' => "bio-$id")) ?>

You are fetching that slot from a separate virtual page with the slug bio-$id, where $id might identify a particular user in the sfGuardUser table.

Apostrophe will automatically create the needed virtual page object in the database the first time the slot is used. This technique can be used for areas as well.

But why is this better than using 'global' => true? Two reasons: performance and access control.

Performance, Global Slots and Virtual Pages

Yes, you could manage dynamic content like biographies with just the global flag, if you chose dynamically generated names for your slots and areas. But since Apostrophe loads all current global slots into memory the first time a global slot is requested on a page, the CMS would be forced to load all of your biographies on just about every page of the site. That's clearly not acceptable. For that reason it's important to use a separate virtual page for each individual's biography, etc.

As a rule of thumb, add database IDs to virtual page slugs, not area or slot names.

Access Control For Global Slots and Virtual Pages

By default, only sitewide admins can edit slots included from other virtual pages. This is fine for headers and footers seen throughout the site, but doesn't work well for biography slots. In other words, users should be able to write their own autobiographies.

You can address this problem by passing 'edit' => true as an option to the slot or area. This overrides the normal slot editing privilege checks, so you should do this only if the user ought to have the privilege according to your own judgment. For instance, you might do something like this:

    <?php $myid = sfContext::getInstance()->getUser()->getGuardUser()->id ?>
    <?php a_slot('biography', 'aRichText', array('tool' => 'basic', 'slug' => "bio-$id", 'edit' => $id === $myid)) ?>

Once again, you can do this with areas as well. Also note that setting 'edit' => false will also disable editing of the slot or areas for admin users as well, this is the behavior in the trunk and will be present in 1.1

Including Slots From Other "Normal" Pages

It's possible to use the slug option to include a slot from another normal, navigable page on the site. However, if you do so, keep in mind that you don't want to create a situation where the same slot is included twice on the page itself.

Also keep in mind that normal pages can be moved around on the site, which will break templates that contain explicit slugs pointing at their old locations. You can avoid this by determining the slug option dynamically, or by using a virtual page instead (no leading / on the slug).

CSS: Styling Apostrophe

By default, apostrophePlugin provides several stylesheets which are automatically added to your pages. (In Apostrophe 1.5 the built-in minifier compiles these and any other stylesheets you add down to one stylesheet in production.) There's a lot happening there, particularly with regard to the editing interface, and we recommend that you keep these and override them as needed in a separate stylesheet of your own (such as the provided main.css starting point in the sandbox project).

However, you can turn them off if you wish in app.yml. In 1.4:

        use_bundled_stylesheet: false

In 1.5 the configuration has changed to be more flexible:

    # Disable ALL bundled CSS and JavaScript. Not recommended.
    # See below for a more selective approach
    use_bundled_stylesheets: false
    # Selectively disable or override various bundled styles and javascripts.
    # Everything you leave alone or set to true loads by default
       admin: true
       forms: false
       components: false
       engines: false
       navigation: false
       area-slots: false
       buttons: false
       colors: false
       utility: /foo/bar/myutility.css

If you do keep our stylesheets and further override them, you'll want to specify position: last for the stylesheets that should override them. This is automatically done for the stylesheet web/css/main.css in our sandbox project, and we recommend you make your customizations there.

Similar overrides are available for JavaScript?, see ManualDevelopersGuide for more information.

Smarter CSS With LESS

Beginning with Apostrophe 1.5, LESS syntax is supported "out of the box." Just add .less files to the list of stylesheets in view.yml in exactly the same way you add regular .css stylesheets.

LESS  began life on Ruby but is  now available for PHP thanks to the lessphp library, which is included with Apostrophe.

You can  learn about the advantages of LESS here. In particular, you can use variables, mixins and nested blocks to drastically reduce the amount of duplicated code in your CSS and make it easy to change colors globally, etc.

In development, with app_a_minify turned off, LESS files will be automatically recompiled on the next page load if they are newer than the compiled CSS files, which live in a separate asset-cache folder to avoid conflicts. In other words, you can edit and refresh and see the impact of your changes and there is no need to run lessc yourself.

In production, with app_a_minify turned on, you must symfony cc after deploying changes to .less files. You already need to do that with our minifier in any case, so there's no new inconvenience here, and performance is enhanced by not checking file modification times on every page load.

Note that the PHP less compiler does take noticeable time to run. Keep in mind that the compiled CSS output is cached, so your end users will not have to wait (unless they happen to be the very first person to come along). If it is a concern you can address the "first person" issue by accessing your own site.

The Golden Rule: Use Classes, Not IDs

You may want to style individual areas and slots without introducing wrapper divs to your templates. To do that, pay attention to the CSS classes we output on the outermost wrappers of each area. You'll note that standalone slots still have an area wrapper in order to implement their editing controls and make it easier to write consistent CSS:

    <div id="a-area-12-body" class="a-area a-area-body">

You may be tempted to use the id attribute. Don't do that. The id attribute contains the page ID, which differs from page to page and should never be used in CSS.

Instead, take advantage of the classes on this div:

      css rules specific to the area or slot named 'body'

Note that if you need different behavior on different pages in a way that can't be achieved by adding different slots and using different variants of each slot, you should use a separate template for each type of page.

Why So Many Wrappers?

The wrapper divs output by Apostrophe are necessary to implement inline editing. We've found over time that it's best to keep them in place even when the page is rendered in a non-editing mode in order to allow a single set of CSS to render the page consistently.

We recommend embracing our wrapper divs and styling them to suit your needs rather than attempting to remove or replace them or put unnecessary wrappers around them. See the provided a/templates/defaultTemplate.php for an example of how to float one to the left of another. The use of CSS floating makes it straightforward to lay slots and areas out as you see fit.

Styling Slot Variants

Slot variants (choices on the "Options" menu for a given slot) also set CSS classes. These classes have the same name as the option. This is very useful for styling purposes, especially if you wish to display the rich text description of a media image to the right or left of the image itself. See "Slot Variants: More Mileage From Your Templates" for details.

CSS: Apostrophe UI

Apostrophe UI colors can be easily changed by overriding a small set of CSS styles bundled with the plugin.

Located at the bottom of a.css you will find this css:

/* 34. aUI & Admin Colors - Default

#a-global-toolbar #the-apostrophe
{ /* Apostrophe */
	background-color: rgb(255,150,0);
	background-color: rgba(255,150,0,0.75);	

ul.a-controls .a-variant-options
{ /* Border Color */
border-color: rgb(255,150,0);

.a-admin .a-admin-content a:link, 
.a-admin .a-admin-content a:visited,
#a-global-toolbar #a-logged-in-as span,
#a-personal-settings-heading span
{ /* Text Color */
color: rgb(255,150,0);

You can simply copy this section from a.css into your site's CSS file and change the values to colors that work for your design.


There is a javascript function aUI(); that decorates the buttons in a cross-browser compatible way on Dom Ready. It lives in aUI.js. By default it touches every button the page and applies functional and aesthetic changes to the buttons. The aUI() call can be scoped using a CSS selector or a jQuery object by simply passing it into the function call.

aUI('.a-area-body'); will only affect buttons that are children of this CSS selector. We use this for updating buttons that are returned via AJAX, missing out on the Dom Ready call.


This function is hooked into aUI() To use it, define the function aOverrides() in a newly created a project level site.js file. If it exists, aUI() will call it whenever aUI() is executed. This is helpful when working with technologies such as Cufon that need to be run on Dom Ready.

CSS: Apostrophe Buttons


  • The base class for all buttons in the Apostrophe UI. When used alone, it will create a simple button out of an any element.
  • examples
    • <A href="" class="a-btn">..text..</A>
    • <INPUT type="SUBMIT" value="..text.." class="a-btn"/>
    • <LI class="a-btn">...text...</LI>
  • .icon
    • Alters the button to make space for a sprite on the left side
    • .a-icon-name
      • A second class is necessary for the .icon class to make sense. A sprite is defined by the icon name class in the a.css file
      • example
        • CSS
          • .a-edit { background-image: url(/apostrophePlugin/images/a-icon-edit.png); }
        • HTML
          • <A href="" class="a-btn icon a-edit">..text..</A>
  • .alt
    • chooses the alternate CSS sprite and button color scheme for the button.
    • some site designs work better with white icons instead of black and using the .alt class in some situations helps improve the buttons legibility on a case-by-case basis. Note: The .alt class can be applied to the <BODY> to globally change all buttons across the site to use the alternate sprite.
  • .nobg
    • removes background and border
    • example
      • <A href="" class="a-btn nobg">..text..</A>
  • .no-label
    • Hides the text label of the button
  • .mini
    • Small button style with 10px type size
  • .big
    • Large button style with 18px type size
    • example
      • <A href="" class="a-btn big">..text..</A>
  • .flag
    • Hides the text label and displays it upon :hover
    • .flag-left
      • Hides the text label and displays it as a tooltip to the left of the button
    • .flag-right
      • Hides the text label and displays it as a tooltip to the right of the button
      • example
        • <A href="" class="a-btn flag flag-right">..text..</A>

Access Control: Who Can Edit What?

By default, an unconfigured Apostrophe site that was not copied from our sandbox follows these security rules:

  • Anyone can view any page without being authenticated.
  • Any authenticated (logged-in) user can edit any page,

and add and delete pages.

This is often sufficient for simple sites. But Apostrophe can also handle more complex security needs.

Requiring Login to Access All Pages

To require that the user log in before they view any page in the CMS, use the following setting in app.yml:

        view_login_required: true

Requiring Login to Access Some Pages

To require the user to log in before accessing a particular page, just navigate to that page as a user with editing privileges and click on the "lock" icon.

By default, locked pages are only accessible to logged-in users. Of course, on some sites this is too permissive, especially when users are allowed to create their own accounts without further approval. In such situations you can set up different credentials to access the pages. To require the view_locked credential to view locked pages, use the following app.yml setting:

        view_locked_sufficient_credentials: view_locked

Then grant the view_locked permission to the appropriate sfGuard groups, and you'll be able to distinguish user-created accounts from invited guests.

Requiring Special Credentials to Edit Pages

Editing rights can be controlled in several ways. It may seem a bit confusing, so keep in mind that the default set of permissions, groups and app.yml settings in our sandbox project works well and allows the admin to assign editing and managing privileges anywhere in the CMS page tree as they see fit. Editing privileges allow users to edit a page, while managing privileges allow them to also create and delete pages.

Read on if you believe you may need to override this configuration. You will need to do that if you are not using our sandbox project as a starting point, as the default behavior of the plugin is to allow any logged-in user to edit as they see fit (often quite adequate for small sites without unprivileged user accounts).

Editing and managing privileges are granted as follows:

1) Any user with the cms_admin credential can always carry out any action in the CMS, regardless of all other settings. Note that the sfGuard "superadmin" user always has all credentials.

2) Any user with edit_sufficient_credentials can always edit pages (but not necessarily add or delete them) anywhere on the site. For instance, if you add such users to the executive_editors sfGuardGroup and grant that group the edit permission, then you can give them full editing privileges with these settings:

        edit_sufficient_credentials: edit

If you do not specify any editing credentials at all, then any logged-in user can edit anywhere. This is useful for small sites.

Similarly, any user with manage_sufficient_credentials can always add or delete pages anywhere on the site, in addition to editing content. So complete settings might be:

        edit_sufficient_credentials: edit
        manage_sufficient_credentials: manage

Note that if you do not specify manage_sufficient_credentials any logged-in user can manage pages anywhere.

For convenience, you can also grant these privileges directly via a group name:

        edit_sufficient_group: executive_editors
        manage_sufficient_group: executive_editors

3) Any user who is a member of the group specified by app_a_edit_candidate_group can potentially be made an editor in particular parts of the site. If app_a_edit_group is not set, all logged-in users are potential editors.

        edit_candidate_group: editors

Similarly, any user who is a member of the group specified by app_a_manage_candidate_group can potentially be given the ability to add and delete pages in a particular part of the site. So a common setup might be:

        edit_candidate_group: editors
        manage_candidate_group: editors

Why is this feature useful? Two reasons: because checking their membership in one group is faster than checking their access privileges in the entire chain of ancestor pages, and because when an administrator is managing the list of users permitted to edit a page the list of users in the editors group is much easier to read than a list of all users (especially in a large system with many non-editing users).

4) Editing privileges for any specific page and its descendants can be granted to any member of the group specified by app_a_edit_candidate_group (if that option is set), or to any user if app_a_edit_candidate_group is not set. When a user with the right to manage a page opens the page settings, they are given the option of assigning editors for that page. The same principle applies to "managing" (adding and deleting) pages, with the candidate group being indicated by your app_a_manage_candidate_group setting.

Note that the pulldown list of possible editors can be quite long if there are thousands of people with accounts on your site! This is why we recommend setting up groups as described above.

Publishing Pages, by Choice and By Default

Apostrophe offers a "published/unpublished" toggle under "manage page settings." Pages that are unpublished are completely invisible to users who do not have at least the candidate credentials to be an editor; a user without appropriate privileges gets a 404 not found error just as if the page did not exist. In most cases you should use this in preference to actually deleting the page because the content is still available if you choose to bring it back later.

By default all new a pages are in the "published" state. If you need to approach the matter more conservatively, you can easily change this with the following app.yml setting:

        default_on: false

Limiting The Depth of the Page Tree

By default, editors can create pages nested as deeply as they wish. This makes sense for a site built by a few skilled people, but when there are many cooks in the kitchen you may want to impose some discipline. You can do that in app.yml by setting the app_a_max_page_levels option:

        # Allows tabs, grandchildren, and great-grandchildren
        max_page_levels: 3

A setting of 3 allows tabs, grandchildren (children of tabs), and great-grandchildren. This is often a good choice. For a very simple site with no breadcrumb trail in the layout, you might want to use max_page_levels: 1, which only permits tabs, or max_page_levels: 2, which only permits tabs and grandchildren.

Limiting the Number of Children of Any One Page

Similarly, you can limit the number of child pages that any given page can have. This helps to avoid unwieldy side navigation and impose a bit more structure.

Set the max_children_per_page option to do this:

        # No page, including the home page, may have more than 8 direct children
        max_children_per_page: 8

Note that only the immediate children of a page are counted against this limit.

Deferring Search Engine Updates

By default pages are reindexed for search purposes at the time edits are made. This makes installation simple, but for performance reasons you might be happier deferring this to a cron job that runs every few minutes. If you want to take this approach, set up a cron job like this:

    0,10,20,30,40,50 '' '' '' '' /path/to/your/project/symfony apostrophe:update-search-index --env=env

Note the --env option. There is a separate index for each environment. On a development workstation, specify --env=env. In a production environment you might specify --env=prod.

Then turn on the feature in app.yml:

        defer_search_updates: true

This speeds up editing a bit. But if you don't like cron, you don't have to enable it.

You can also change the word count of search summaries:

        search_summary_wordcount: 50

Rebuilding the Search Index

When you deploy from development to production, you will need to build a new search engine index for production. This is normally a one-time operation:

./symfony apostrophe:rebuild-search-index --env=prod

If you choose to sync your content to or from staging and production servers with sfSyncContentPlugin, you'll need to run this task with the appropriate environment parameter for the host in question after syncing content to it.


That's it for the front end designer oriented section of the manual. Next we'll move on to information for PHP developers who want to extend Apostrophe with new capabilities and integrate it more tightly into their own websites.

Continue to Developer's Guide

Up to Overview