Your web browser is out of date. Update your browser for more security, speed and the best experience on this site.

Update your browser

Apr 01, 2019 • By Jake Dohm

Creating an API with Twig

An illustration of a computer making a physical abstract API

Creating an API can be tricky. You have to install the Element API plugin, configure it and set up your endpoints. Not to mention that if you're not as familiar with PHP it can be difficult to figure out how to query for what you need!

On the other hand Twig APIs are easy to create and built in a language you already know! This type of API doesn't work for every scenario, and it can feel a bit 'hacky' at times, but for simple APIs Twig is a great solution.

Basic API

So, how should you go about creating an API in Twig? Since Craft allows users to hit Twig templates directly by going to their file path, all you need to do is create a Twig file.

Basic Example

For a super simple example you can create a Twig file templates/api/entries.json with the following contents:

{# templates/api/entries.json #}

{% set entries = craft.entries.all() %}

{{ entries | json_encode | raw }}

In this example we're querying for all our entries and encoding the results of that query to be JSON, using raw to allow HTML characters.

This is probably an oversimplified example, because this method doesn't return all of the fields on each entry. Fields like 'assets' and 'entries' have to be queried for, and won't be returned from this basic API. But it's a great starting point, and can work for very basic scenarios.

Note: This is a JSON API, but it could be any type. You could manually build out XML or another data type (and I have), but since JSON is the most popular data type for APIs we'll be returning JSON.

API with Params

In our basic example the API grabs all entries, but that isn't a very common use-case. A much more common example is getting all entries from a certain section. In the following example we'll add the ability to query for entries in a certain section, and limit how many entries we return.

{# templates/api/entries.json #}

{% set request = craft.app.request %}

{% set section = request.getQueryParam('section') %}
{% set limit = request.getQueryParams('limit') %}

{% set entries = craft.entries.section(section).limit(limit).all() %}

{{ entries | json_encode | raw }}

Alright, let's look at what we did above:

  • We're defining 'section' and 'limit' variables based on query params. This means that if someone visits example.com/api/entries.json?section=blog&limit=5 then 'section' will be set to 'blog' and 'limit' will be set to 5.
  • We then pass those variables into our 'entries' query, and return the 'entries' query.

Note: When you call getQueryParam if a param with that name doesn't exist, it returns null. This is convenient because it means if you hit our endpoint without a 'section' or 'limit' param the API will still work and return all entries with no limit.

Adding Authentication

Sometimes the data you want to expose shouldn't be available to just anyone. In this case we want to add a level of authentication to make sure the user is logged in.

Since the request to our Twig endpoint will be made in the same session as our normal pages we can use the currentUser variable to determine whether the user making the request is logged in or not.

{% if currentUser %}
 {% set request = craft.app.request %}

 {% set section = request.getQueryParam('section') %}
 {% set limit = request.getQueryParam('limit') %}

 {% set entries = craft.entries.section(section).limit(limit).all() %}

 {{ entries | json_encode | raw }}
{% else %} 
 {% exit 401 %}
{% endif %}

In the above code we check to see if there is a currentUser. If so we return the entries like normal; if not we throw an error. You can customize this error response, but I'm throwing a '401 error' meaning the request was unauthorized.

Note: This is a very crude authorization system with very mediocre 'error' reporting (throwing a 401 template). For something robust I would lean on a more full-featured API system.

Real World Example (with SmartMap)

Finally, here's a slightly more realistic example I've previously implemented in production!

Below is a Twig API that takes two parameters: a search value and a limit. It will use SmartMap (todo: link) to search for locations nearest to the search string, order them by distance, and return them to us in JSON.

{# Get our paramater values #}
{% set search = craft.app.request.getQueryParam('search') %}
{% set limit = craft.app.request.getQueryParam('limit') %}

{# Build our query, based on parameters #}
{% set locations = craft.entries.mapAddress({ target: search }).orderBy('distance').limit(limit).all() %}

{# Create an empty array to fill with our data #}
{% set locationsArray = [] %}

{# Loop over our items and fill our empty array with the necessary data #}
{% for location in locations %}

 {# Choose the properties that we need from our API #}
 {% set locationsArray = locationsArray | merge([{
 title: location.title,
 url: location.url
 }]) %}

{% endfor %}

{# Output JSON encoded array of locations #}
{{ locationsArray | json_encode | raw }}

I like this example because it shows all the steps we generally need to build the necessary data:

  1. Get our parameters (if any)
  2. Build our query based on parameters
  3. Create an empty object to fill
  4. Loop over our items and fill our empty array with the necessary data
  5. Output our data as JSON

Conclusion

Does this replace the Element API plugin, Craft QL, or creating your own API endpoints? No, definitely not. Those are all powerful tools that do more/better than this method. However, this method is great for a simple API, or when you've already figured out how to query for something in Twig and don't want to convert that code to PHP. Use it in good health at your discretion!

An illustration of Donkeytail for Craft CMS + Tailwind and Alpine JS

Further Reading

Jan 19, 2022 • By Robin Mannering

A practical example of using the Donkeytail plugin with Tailwind and Alpine JS

Overview In this article we'll explore a practical example of using the Donkeytail plugin to display points of interest (pins) on an image of a lounge, highlighting the different pieces of furniture for sale and labelling the name of each item with a short description and price. We'll use Tailwind to style each Donkeytail pin, and Alpin…

Don’t let chaotic web projects get you down.

Get a web project — and a development process — that wows.

Sign up for our newsletter.

We send a few emails every month with helpful articles and resources for people who make and manage websites.