DEPRECATED, for legacy 0.5 sites only. Create sites powered by the Apostrophe 0.5 CMS with a minimum of boilerplate code


You do not need this module for your new Apostrophe project and it will not work with it. See the apostrophe documentation for more information about Apostrophe 2.x which has all of the capabilities of this module built right in.

Apostrophe Site

The Apostrophe Site module provides most of the scaffolding for projects built with Apostrophe 0.5.x, an OLD version of the Apostrophe content management system. You do not want this for your new project.


Don't, you should only find this module in a legacy project you are maintaining. These are old instructions.

It is easiest to start by cloning the Apostrophe sandbox project and pushing it up to your own repository. But skipping that step is pretty easy too. Let's assume you're starting from scratch.

Apostrophe itself requires:

  • node, of course. You must have at least version 0.10
  • mongodb version 2.2 or better, on your local machine (or point to another database server)
  • imagemagick, to resize uploaded images (specifically the convert and identify command line tools)

Create a new git project, then run npm install apostrophe-site to install the module.

Configuring Your Site

Here's an app.js that demonstrates most of the options. Most of this is optional, of course. root, shortName, hostName, adminPassword and sessionSecret are required, and you almost certainly will want to add a few modules. You shoudl also set baseUrl if the protocol for your site is not HTTP. Everything else is totally skippable.

    var site = require('apostrophe-site')({
      // Allows apostrophe-sites to require stuff
      // on our behalf and also find our root folder
      root: module,

      // Used to name the local mongodb database,
      // if you don't pass a db option with more details
      shortName: 'mysite',

      // Hostname you plan to give your site
      hostName: 'mysite.com',

      // If we don't set this, we get http://mysite.com
      baseUrl: 'https://mysite.com',

      // Title of your site. Used as a prefix to page titles and feed titles by default
      title: 'My Site',

      // This defaults to true and delivers HTML, CSS and JS much faster via
      // gzip transfer encoding. But you can set it to false if you must
      compress: true,

      // Apostrophe sizes your images to several awesome sizes right out of the box,
      // but we're greedy and we want something bigger than full (1280)
      addImageSizes: [
          name: 'max',
          width: 1600,
          height: 1280

      // By default the media library shows everyone's media until the user decides to
      // change that with the "uploaded by" filter. Want the default to go the other way?
      // Set the "owner" option as shown commented out below

      mediaLibrary: {
        // owner: 'user'

      // Normally anyone who can edit a page or article etc. might
      // introduce new tags. If this is set true, new tags can only
      // be introduced via the admin tag editor
      lockTags: false,

      // Set up email transport via nodemailer. By default sendmail is used because
      // it requires no configuration, but you may use any valid transport, see the
      // nodemailer module documentation.

      mailer: {
        transport: 'sendmail',
        transportOptions: {}

      // You can always log in at /login as admin, with this password
      adminPassword: 'SOMETHING SECURE PLEASE',

      // If a visitor tries to access a secured page, give them
      // a chance to log in and then be redirected to that page
      secondChanceLogin: true,

      // Invoked after login if secondChanceLogin is not set or
      // did not result in a page the user was allowed to see
      redirectAfterLogin: function(req, callback) {
        if (req.user.permissions.admin) {
          return callback('/awesomepeople');
        } else {
          return callback('/coolpeople');

      // Run some middleware on ALL requests. This happens AFTER
      // basics are in place like sessions and users and i18n.
      // Middleware functions here may take an initial "site" argument
      // in addition to req, res, next. Modules may also provide
      // middleware simply by setting a "middleware" property on
      // themselves

      middleware: [ /* middleware, functions, galore */ ],

      sessionSecret: 'SOMETHING RANDOM PLEASE',

      // Minify all CSS and JS into a single file each (can be fine-tuned
      // with other options). Great in production
      minify: true,

      // If the generated CSS has more than 4,095 rules, split into
      // multiple imported CSS files to avoid a limitation of IE9 and below.
      // This is disabled by default
      bless: false,

      // Any options accepted by the apostrophe-pages module,
      // such as tabOptions and descendantOptions
      pages: {
        // List all the page types users should be able to add here, including
        // things like "Blog" and "Events" that are powered by modules, so you get
        // to pick the order
        types: [
          // TODO double check this doesn't get ignored if blog is added later and wasn't wanted
          { name: 'default', label: 'Default (Two Column)' },
          { name: 'home', label: 'Home Page' },
          { name: 'blog', label: 'Blog' },
          { name: 'events', label: 'Events' }

        // Load descendants of homepage and current page two levels deep
        // instead of one
        tabOptions: { depth: 2 },
        descendantOptions: { depth: 2 },

        // Do something special if the URL doesn't match anything else
        notfound: function(req, callback) {
          if (req.url === '/special') {
            req.redirect = '/specialer';
          return callback(null);

        // Run some middleware on the route that serves pages.
        // This is not global middleware, see the top-level middleware option.

        // Middleware functions may take an initial "site" argument
        // in addition to req, res, next. Modules may also register
        // page-serving middleware simply by setting a
        // pageMiddleware property on themselves

        middleware: [ /* middleware, functions, galore */ ],

        // Custom page loader functions beyond those automatically
        // provided. Already you have the page with the slug 'global'
        // available at all times, the current page, its tabs, its
        // descendants, and anything loaded on behalf of your modules,
        // like blog posts appearing on the current page
        load: [
          function(req, callback) {
            if (!(req.page && (req.page.type === 'fancy'))) {
              // Doesn't concern us
              return callback(null);
            // Set some custom data to be provided to the nunjucks template.
            // Anything in the extras object is pushed as data to the
            // page template.
            // We have a callback here, so we could go get anything
            req.extras.fanciness = true;
            return callback(null);

      // Let's add the blog and events modules. You must npm install them.
      // apostrophe-site will require them for you and pass your options
      modules: {
        'apostrophe-events': {
          widget: true
        'apostrophe-blog': {
          widget: true

      // Custom command line tasks. Run like this:
      // node app project:frobulate
      // argv is powered by optimist
      tasks: {
        project: {
          frobulate: function(apos, argv, callback) {
            console.log('Frobulated the hibblesnotz');
            console.log('You passed these arguments: ' + argv._);
            return callback(null);

      locals: {
        // Extra locals visible to every nunjucks template. Functions and
        // data are both fair game. You may also pass a function that takes
        // the site object as its sole argument and returns an object containing
        // the desired locals as properties.
        embiggen: function(s) {
          return s * 1000;

      assets: {
        // Loads site.js from public/js
        scripts: [
          // load this js file all the time, minify it normally
            // Load this JS file only when a user is logged in, never minify it.
            // 'when' could also be 'always'. 'minify' defaults to true
            name: 'fancy',
            when: 'user',
            minify: false
        // Loads site.less from public/css
        stylesheets: [

      // Last best chance to set custom Express routes
      setRoutes: function(callback) {
        site.app.get('/wacky', function(req, res) { res.send('wackiness'); });
        return callback(null);

      // Just before apos.endAsset. Last chance to push any assets. Usually the
      // `assets` option above, and calling `pushAsset` from your modules,
      // is good enough.

      beforeEndAssets: function(callback) {
        // Apostrophe already loads these for logged-out users, but we
        // want them all the time in this project.
        site.apos.pushAsset('script', { name: 'vendor/blueimp-iframe-transport', when: 'always' });
        site.apos.pushAsset('script', { name: 'vendor/blueimp-fileupload', when: 'always' });
        return callback(null);

      // Just before listen. Last chance to set up anything
      afterInit: function(callback) {
        return callback(null);

      sanitizeHtml: {
        // Any options that can be passed to the sanitize-html
        // module are valid here. Used to adjust the way we filter
        // HTML saved in the rich text editor. You probably want
        // to stick with our standard set of allowed tags and
        // encourage users to respect your design rather than
        // fighting it

      // A simple way to alter the results of every call to apos.get, and thus
      // every page, snippet, blog post, etc. The retrieved documents will be
      // in results.pages. Be aware that this property does not always exist,
      // as apos.get is sometimes used just to fetch distinct tags or
      // other metadata.
      afterGet: function(req, results, callback) {


Two-Step Configuration

If you prefer you can configure Apostrophe in two steps:

var site = require('apostrophe-site')();
site.init({ ... same configuration as above ... });

This allows you to pass your site object to functions implemented in other files in order to create parts of your configuration:

// in app.js

var site = require('apostrophe-site')();
  // ... regular stuff ...
  pages: {
    load: require('./lib/loaders.js')(site)

// in lib/loaders.js

module.exports = function(site) {
  return [
    function(req, callback) {

Adding Modules to the Admin Bar

Adding a module to the modules property above does most of the work, but you do need to add it to the admin bar when appropriate. For instance, you'll want the "blog" menu to be added at the top of the page when the blog module is installed.

In our sandbox site or a project cloned from it, you would do that in outerLayout.html. Just look for calls like this one:

{{ aposBlogMenu({ edit: permissions.edit }) }}

Conversely, if you choose not to include a module but haven't removed it from the admin bar, don't be surprised when you get a template error.

Overriding the Templates of a Module

First npm install and configure apostrophe-blog. Then create a lib/modules/apostrophe-blog/views folder in your project. Copy any templates you wish to customize from the npm module's views folder to lib/modules/apostrophe-blog/views.

Boom! Apostrophe will automatically look first at your "project level" module folder.

This also works for apostrophe-schemas and apostrophe-pages, even though they are not configured by the modules property. lib/modules/apostrophe-schemas/views may contain overrides for schema field templates, and lib/modules/apostrophe-pages/views may contain overrides for newPageSettings.html and friends.

Overriding a Module With a New Name

You can override a module more than once, for instance to set up two things that are similar in spirit to a blog. Just create folders in lib/modules, with your views overrides, and configure them in app.js via the modules option as shown above. Then use the extend property to tell Apostrophe what module you're extending.

You'll want to set the name and instance options so the database can distinguish between your stories and regular blog posts:

    stories: {
      extend: 'apostrophe-blog',
      name: 'stories',
      instance: 'story',
      addFields: [
          name: 'storyteller',
          type: 'string'

Note that you will need to copy the new, edit and manage templates to your views folder and fix any references to blog and blog-post to refer to stories and story.

Overriding the Schema of a Module: Adding Custom Properties

As seen above, you can add and alter the properties of blog posts and similar things via the addFields and alterFields options as described in the apostrophe-snippets documentation. Those options can go right in the configuration for your module in app.js.

Overriding and Extending Methods of a Module

If you really need to change a module's behavior, for instance changing what the page loader function does or the way it fetches data from the database, you'll need to subclass it. But we've made subclassing much easier. Just create an index.js file in your lib/modules/mymodulename folder.

Here's a really simple subclass that changes the way the index method of the blog behaves so that a featured story is available to the index.html template as the featured variable in nunjucks:

    module.exports = stories;

    function stories(options, callback) {
      return new stories.Stories(options, callback);

    stories.Stories = function(options, callback) {
      var self = this;

      module.exports.Super.call(this, options, null);

      var superIndex = self.index;
      self.index = function(req, snippets, callback) {
        self.get(req, { tags: 'featured' }, { limit: 1 }, function(err, results) {
          if(err) {
          if(results.total > 0) {
            req.extras.featured = results.snippets[0];
          superIndex(req, snippets, callback);

      // Must wait at least until next tick to invoke callback!
      if (callback) {
        process.nextTick(function() { return callback(null); });


Note the use of module.exports.Super. This automatically points to the base class constructor.

Confused? Just remember to follow this pattern and put your method overrides after the call to module.exports.Super.

Tip: Subclassing Snippets is Often a Good Idea

If it doesn't smell like a blog post, you probably want to subclass snippets instead. The blog module simply subclasses snippets and adds the idea of a publication date.

Modules Can Have Nothing To Do With Snippets

You can configure modules that have nothing at all to do with snippets, too. Our own RSS and Twitter modules, for instance.

To configure a module with apostrophe-site, all you have to do is make sure it looks like this:

    module.exports = factory;

    function factory(options, callback) {
      return new Construct(options, callback);

    function Construct(options, callback) {
      var self = this;
      // Add a bunch of methods to self here, then...

      // Invoke the callback. This must happen on next tick or later!
      return process.nextTick(function() {
        return callback(null);

    // Export the constructor so others can subclass
    factory.Construct = Construct;

In a nutshell: you must export a factory function, and it must have a constructor as its Construct property.

Options Provided to Modules

In addition to the options you specify in app.js, all modules receive:

apos: the apos object, a singleton which provides core methods for content management. See the apostrophe module documentation.

pages: the pages object, a singleton which provides methods for dealing with the page tree. See the apostrophe-pages module documentation.

schemas: the schemas object, a singleton which provides methods for dealing with schemas. Most of the time you won't interact with this directly, but you might if you're writing a module that handles moderated submissions and the like. See the apostrophe-schemas module documentation.

mailer: a nodemailer transport object, ready to send email as needed. See the nodemailer documentation.

site: an object containing title, shortName and hostName properties, as configured in app.js.

modules: an array of objects with web and fs properties, specifying the web and filesystem paths to each folder in the chain of overrides, which is useful if you wish to allow project-level overrides via lib/modules of views provided by an npm module. You can take advantage of this easily if you use the mixinModuleAssets and serveAssets mixins; see assets.js in the apostrophe module for documentation.

Accessing Other Modules

After all modules have been initialized, apostrophe-site calls the setBridge method on each module that has one. This method receives an object containing all of the modules as properties. The people module, for instance, uses the bridge to access the groups module. Note that this is not called until after all modules have invoked their initialization callback.

Publishing Modules

You can write custom modules in lib/modules for your project-specific needs, or install them with npm. If you use lib/modules, your module's code must load from lib/modules/mymodulename/index.js.


Currently extend does not check lib/modules, so the module you are extending must be published in npm. Most of the time we extend modules like apostrophe-blog and apostrophe-snippets in simple project-specific ways, so this isn't much of a problem so far.


Using i18n is simple you enable it by adding the following in your apostrophe-site configuration in app.js:

i18n: {
    // setup some locales - other locales default to defaultLocale silently
    locales:['en', 'de'],

    // you may alter a site wide default locale (optional, defaults to 'en')
    defaultLocale: 'de',

    // sets a custom cookie name to parse locale settings from  - defaults to apos_language (optional)
    cookie: 'yourcookiename',

    // whether to write new locale information to disk automatically - defaults to true (you will want to shut it off in production)
    // updateFiles: false

After doing this, you can internationalise text in your own templates with:

{{ __('A sample string') }}

The __ local will take care of language detection and will spit out the appropriate string from the JSON files that will be located in the locales folder of your project by default. If you look in that folder, you'll see multiple JSON files with a two letter language abbreviation as a filename, for instance:


Those will contain all the necessary strings. By default, i18n will automatically put anything new it finds there. However, you can disable this behaviour by setting updateFiles to false.

More Modules, More Documentation

See apostrophe, apostrophe-sandbox, apostrophe-pages, apostrophe-snippets, apostrophe-blog, apostrophe-events, apostrophe-map, apostrophe-groups, apostrophe-people, apostrophe-rss and apostrophe-twitter.

Also browse the apostrophe tag on npm.


You should join the apostrophenow Google Group for discussion of both Apostrophe 1.5 and Apostrophe 2.

Thanks for using Apostrophe!

P'unk Avenue





  • 运行uni-app报错:sitemap.json Error: 未找到入口 sitemap.json 文件

    运行项目报错sitemap.json Error: 未找到入口 sitemap.json 文件,或者文件读取失败,请检查后重新编译。 解决方法:在pages.json 文件中添加属性"sitemapL...

    6 个月前
  • 用Node.js通过sitemap.xml批量抓取美女图片

    之前看了很多个版本,自己也搞一个。 1. 支持指定保存到哪个目录 2. 按文章进行分目录存放 3. 支持设置并行下载上限 下次有空再搞个整站下载的。 package.json { "name": "...

    4 年前
  • 浏览器渲染流程&Composite(渲染层合并)简单总结

    梳理浏览器渲染流程 首先简单了解一下浏览器请求、加载、渲染一个页面的大致过程: DNS 查询 TCP 连接 HTTP 请求即响应 服务器响应 客户端渲染 这里主要将客户端渲染展开梳理一下,从浏...

    2 年前
  • 当跨域遇到Cookie与SameSite

    简介 对于切图仔而言,跨域是个非常熟悉的名词了。虽然浏览器为了我们的网站安全操碎了心,但是往往我们为了网站能够被用户正常访问,不得不绕过这个限制,cors就是其中一种常用的解决跨域的方案。

    9 个月前
  • 当 CORS 遇到 SameSite

    发现问题 最近一年做项目一直都是 CORS 一把梭,非常快乐。 毕竟只要设置 withCredentials,预先请服务端同学加下 CORS 白名单,不管开发环境、测试环境还是线上环境,都可以直接请求...

    6 个月前
  • 学习 canvas 的 globalCompositeOperation 做出的神奇效果

    说明 最早知道 canvas 的 globalCompositeOperation 属性,是在需要实现一个刮刮卡效果的时候,当时也就是网上找到刮刮卡的效果赶紧完成任务就完了,这次又学习一次,希望能加深...

    2 年前
  • 如何区分两个地址是同站(Same site)还是跨站(Cross site)?

    今天在掘金上读到了 一篇文章《Cookie 的 SameSite 属性》,写得挺好。 对下面一段话,我还想做点补充。 Cookie中的「同站」判断比较宽松:只要两个 URL 的 eTLD+1 相同即可...

    6 个月前
  • 为 Next.js 应用生成 robots.txt 和 sitemap.xml

    原文发布于 https://www.imlc.me/p/generate-robots-txt-and-sitemap-xml-in-next-js-zh 为了优化 SEO,生成 robots.tx...

    1 年前
  • v8-callsites

    V8 stacktrace API callsites with knobs v8-callsites V8 stacktrace API callsites with knobs. insta...

    3 天前
  • using reserved words as property names, revisited

    BobStein-VisiBonecc young提出了一个问题:using reserved words as property names, revisited,或许与您遇到的问题类似。

    2 年前


扫码加入 JavaScript 社区