diff --git a/modules/jsonapi_twig/jsonapi_twig.info.yml b/modules/jsonapi_twig/jsonapi_twig.info.yml
new file mode 100644
index 0000000..5f772b4
--- /dev/null
+++ b/modules/jsonapi_twig/jsonapi_twig.info.yml
@@ -0,0 +1,3 @@
+name: JSON Twig
+core: 8.x
+type: module
diff --git a/modules/jsonapi_twig/jsonapi_twig.services.yml b/modules/jsonapi_twig/jsonapi_twig.services.yml
new file mode 100644
index 0000000..d7379a2
--- /dev/null
+++ b/modules/jsonapi_twig/jsonapi_twig.services.yml
@@ -0,0 +1,5 @@
+services:
+  jsonapi_twig.twig_extension:
+    class: \Drupal\jsonapi_twig\TwigExtension
+    tags:
+      - { name: twig.extension }
diff --git a/modules/jsonapi_twig/src/TwigExtension.php b/modules/jsonapi_twig/src/TwigExtension.php
new file mode 100644
index 0000000..6db3a3b
--- /dev/null
+++ b/modules/jsonapi_twig/src/TwigExtension.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\jsonapi_twig;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Entity\EntityInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+class TwigExtension extends \Twig_Extension {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return 'jsonapi';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFunctions() {
+    return [
+      new \Twig_SimpleFunction('api', [$this, 'fetchFromJsonApi']),
+      new \Twig_SimpleFunction('jsonapi', [$this, 'fetchFromJsonApi']),
+    ];
+  }
+
+  public function fetchFromJsonApi($resource, array $options = []) {
+    return \Drupal::service('jsonapi.resource.to_json', ['resource' => $resource])->json();
+  }
+
+}
diff --git a/modules/jsonapi_twig/tests/Kernel/JsonapiTwigIntegrationTest.php b/modules/jsonapi_twig/tests/Kernel/JsonapiTwigIntegrationTest.php
new file mode 100644
index 0000000..1b7d739
--- /dev/null
+++ b/modules/jsonapi_twig/tests/Kernel/JsonapiTwigIntegrationTest.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Drupal\Tests\jsonapi_twig\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\node\Entity\Node;
+use Drupal\node\Entity\NodeType;
+use Drupal\user\Entity\Role;
+
+/**
+ * @group jsonapi_twig
+ */
+class JsonapiTwigIntegrationTest extends KernelTestBase {
+
+  public static $modules = ['system', 'jsonapi', 'jsonapi_twig', 'serialization', 'user', 'node'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('node');
+
+    NodeType::create([
+      'type' => 'article'
+    ])->save();
+
+    $this->installConfig('user');
+    Role::load('anonymous')
+      ->grantPermission('access content')
+      ->save();
+  }
+
+  public function providerTestTemplateFunction() {
+    $data = [];
+
+    $template = <<<TWIG
+{% set data = api('node/article/' ~ node.uuid.value) %}
+uuid: {{ data.data.attributes.uuid }}
+title: {{ data.data.attributes.title }}
+TWIG;
+
+    $expected = <<<OUTPUT
+uuid: 0D5DAE5F-DAC6-4216-8D5B-76A01AF12ED3
+title: Test title
+OUTPUT;
+    $data['url'] = [$template, $expected];
+
+    $template = <<<TWIG
+{% set data = api(node, {fields: {'node--article': 'title,uuid'}}) %}
+uuid: {{ data.data.attributes.uuid }}
+title: {{ data.data.attributes.title }}
+TWIG;
+
+    $expected = <<<OUTPUT
+uuid: 0D5DAE5F-DAC6-4216-8D5B-76A01AF12ED3
+title: Test title
+OUTPUT;
+    $data['node-object'] = [$template, $expected];
+
+    $template = <<<TWIG
+{% set data = api('node/article/' ~ node.uuid.value, {fields: {'node--article': 'title,uuid'}}) %}
+uuid: {{ data.data.attributes.uuid }}
+title: {{ data.data.attributes.title }}
+TWIG;
+
+    $expected = <<<OUTPUT
+uuid: 0D5DAE5F-DAC6-4216-8D5B-76A01AF12ED3
+title: Test title
+OUTPUT;
+    $data['url-sparse'] = [$template, $expected];
+
+    return $data;
+  }
+
+  /**
+   * @dataProvider providerTestTemplateFunction
+   */
+  public function testTemplateFunction($template, $expected) {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
+    $node = Node::create([
+      'type' => 'article',
+      'title' => 'Test title',
+      'uuid' => '0D5DAE5F-DAC6-4216-8D5B-76A01AF12ED3',
+      'status' => NODE_PUBLISHED,
+    ]);
+    $node->save();
+
+    $build = [
+      '#type' => 'inline_template',
+      '#template' => $template,
+      '#context' => [
+        'node' => $node,
+      ],
+    ];
+
+
+    $this->assertEquals($expected, (string) $renderer->renderPlain($build));
+  }
+
+}
diff --git a/tests/themes/jsonapi_twig_test/jsonapi_twig_test.info.yml b/tests/themes/jsonapi_twig_test/jsonapi_twig_test.info.yml
new file mode 100644
index 0000000..b9f8af7
--- /dev/null
+++ b/tests/themes/jsonapi_twig_test/jsonapi_twig_test.info.yml
@@ -0,0 +1,4 @@
+name: JSONAPI twig test
+core: 8.x
+package: Testing
+type: theme
diff --git a/tests/themes/jsonapi_twig_test/templates/node--article.html.twig b/tests/themes/jsonapi_twig_test/templates/node--article.html.twig
new file mode 100644
index 0000000..17ddc1d
--- /dev/null
+++ b/tests/themes/jsonapi_twig_test/templates/node--article.html.twig
@@ -0,0 +1,8 @@
+{#
+// Theoretically this would work for sparse fieldsets.
+{% set data = api(node, {fields: {'node--article': 'title'}}) %}
+#}
+{% set data = api(node) %}
+{#{{ dump(data) }}#}
+<h2>Title: {{ data.data.attributes.title }}</h2>
+<strong>Created: {{ data.data.attributes.created | format_date }}</strong>
