diff --git a/docs/source/_data/sidebar.yml b/docs/source/_data/sidebar.yml
index 53e25330b..1b6d0717e 100644
--- a/docs/source/_data/sidebar.yml
+++ b/docs/source/_data/sidebar.yml
@@ -9,6 +9,9 @@ tutorials:
advanced:
caching: caching.html
registeration: register-filters-tags.html
+ access_scope_in_filters: access-scope-in-filters.html
+ parse_parameters: parse-parameters.html
+ sync_and_async: sync-and-async.html
whitespace: whitespace-control.html
plugins: plugins.html
operators: operators.html
diff --git a/docs/source/tutorials/access-scope-in-filters.md b/docs/source/tutorials/access-scope-in-filters.md
new file mode 100644
index 000000000..fa95c5491
--- /dev/null
+++ b/docs/source/tutorials/access-scope-in-filters.md
@@ -0,0 +1,32 @@
+---
+title: Access Scope in Filters
+---
+
+As covered in [Register Filters/Tags][register-filters], we can access filter arguments directly in filter function like:
+
+```javascript
+// Usage: {{ 1 | add: 2, 3 }}
+// Output: 6
+engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
+```
+
+When it comes to stateful filters, for example transform a URL path to full URL, we'll need to access a `origin` in current scope:
+
+```javascript
+// Usage: {{ '/index.html' | fullURL }}
+// Scope: { origin: "https://liquidjs.com" }
+// Output: https://liquidjs.com/index.html
+
+engine.registerFilter('fullURL', function (path) {
+ const origin = this.context.get(['origin'])
+ return new URL(path, origin).toString()
+})
+```
+
+See this JSFiddle:
+
+{% note warn Arrow Functions %}
+this in arrow functions is bound to current JavaScript context, you'll need to use function(){} instead of ()=>{} syntax to access this.context correctly.
+{% endnote %}
+
+[register-filters]: /tutorials/register-filters-tags.html
diff --git a/docs/source/tutorials/parse-parameters.md b/docs/source/tutorials/parse-parameters.md
new file mode 100644
index 000000000..f347d2fe2
--- /dev/null
+++ b/docs/source/tutorials/parse-parameters.md
@@ -0,0 +1,100 @@
+---
+title: Parse Parameters
+---
+
+## Access Raw Parameters
+
+As covered in [Register Filters/Tags][register-tags], tag parameters is available on `tagToken.args` as a raw string. For example:
+
+```javascript
+// Usage: {% random foo bar coo %}
+// Output: "foo", "bar" or "coo"
+engine.registerTag('random', {
+ parse(tagToken) {
+ // tagToken.args === "foo bar coo"
+ this.items = tagToken.args.split(' ')
+ },
+ render(context, emitter) {
+ // get a random index
+ const index = Math.floor(this.items.length * Math.random())
+ // output that item
+ emitter.write(this.items[index])
+ }
+})
+```
+
+Here's a JSFiddle version:
+
+## Parse Parameters as Values
+
+Sometimes we need more dynamic tags and want to pass values to the custom tag instead of static strings. Variables in LiquidJS can be literal (string, number, etc.) or a variable from current context scope.
+
+The following modified template also contains 3 values to random from, but they're values instead of static strings. The first one is string literal, second one is an identifier, third one is a property access sequence containing two identifiers.
+
+```liquid
+{% random "foo" bar obj.coo %}
+```
+
+It can be tricky to parse all these cases manually, but there's a [Tokenizer][Tokenizer] class in LiquidJS you can make use of.
+
+```javascript
+const { Liquid, Tokenizer, evalToken } = require('liquidjs')
+engine.registerTag('random', {
+ parse(tagToken) {
+ const tokenizer = new Tokenizer(tagToken.args)
+ this.items = []
+ while (!tokenizer.end()) {
+ // here readValue() returns a LiteralToken or PropertyAccessToken
+ this.items.push(tokenizer.readValue())
+ }
+ },
+ * render(context, emitter) {
+ const index = Math.floor(this.items.length * Math.random())
+ const token = this.items[index]
+ // in LiquidJS, we use yield to wait for async call
+ const value = yield evalToken(token, context)
+ emitter.write(value)
+ }
+})
+```
+
+Calling this tag in scope `{ bar: "bar", obj: { coo: "coo" } }` yields exactly the same result as the first example. See this JSFiddle:
+
+{% note info Async ans Promises %}
+Async calls in LiquidJS are implemented by generators directly, for we can call generators in synchronous manner so this tag implementation is also valid for `renderSync()`, `parseAndRenderSync()`, `renderFileSync()`. If you need to await a promise in tag implementation, simply replace `await somePromise` with `yield somePromise` and keep `* render()` instead of `async render()` will do the trick. See Sync and Async for more details.
+{% endnote %}
+
+## Parse Key-Value Pairs as Named Parameters
+
+Named parameters become very handy when there're optional parameters or lots of parameters, in which case the order of parameters is not important. This is exactly what [Hash][Hash] class is invented for.
+
+```liquid
+{% random from:2, to:max %}
+```
+
+In the above example, we're trying to generate a random number in the range [2, max]. We'll use `Hash` to parse `from` and `to` parameters.
+
+```javascript
+const { Liquid, Hash } = require('liquidjs')
+
+engine.registerTag('random', {
+ parse(tagToken) {
+ // parse the parameters structure into `this.args`
+ this.args = new Hash(tagToken.args)
+ },
+ * render(context, emitter) {
+ // evaluate the parameters in `context`
+ const {from, to} = yield this.args.render(context)
+ const length = to - from + 1
+ const value = from + Math.floor(length * Math.random())
+ emitter.write(value)
+ }
+})
+```
+
+Rendering `{% random from:2, to:max %}` in scope `{ max: 10 }` will generate a random number in the range [2, 10]. See this JSFiddle:
+
+
+[register-tags]: /tutorials/register-filters-tags.html
+[Tokenizer]: /api/classes/parser_tokenizer_.tokenizer.html
+[Hash]: /api/classes/template_tag_hash_.hash.html
diff --git a/docs/source/tutorials/register-filters-tags.md b/docs/source/tutorials/register-filters-tags.md
index 7d1e21151..961ca1ada 100644
--- a/docs/source/tutorials/register-filters-tags.md
+++ b/docs/source/tutorials/register-filters-tags.md
@@ -61,4 +61,4 @@ function disabledFilter(name) {
}
}
engine.registerFilter('plus', disabledFilter('plus'));
-```
+```
\ No newline at end of file
diff --git a/docs/source/tutorials/sync-and-async.md b/docs/source/tutorials/sync-and-async.md
new file mode 100644
index 000000000..1929dd9a9
--- /dev/null
+++ b/docs/source/tutorials/sync-and-async.md
@@ -0,0 +1,90 @@
+---
+title: Sync and Async
+---
+
+LiquidJS supports both sync and async evaluate, and can be used with Promises. To reuse the same set of tag/filter implementations in both sync and async, LiquidJS tags are implemented as generators.
+
+## Sync and Async API
+
+All major methods on [Liquid][Liquid] supports both sync and async. These methods return Promises:
+
+- `render()`
+- `renderFile()`
+- `parseFile()`
+- `parseAndRender()`
+- `evalValue()`
+
+The synchronous version of methods contains a `Sync` suffix:
+
+- `renderSync()`
+- `renderFileSync()`
+- `parseFileSync()`
+- `parseAndRenderSync()`
+- `evalValueSync()`
+
+## Implement Sync-Compatible Tags
+
+### Requirements
+
+All builtin tags are *sync-compatible* and safe to use for both sync and async APIs. To make your custom tag *sync-compatible*, you'll need to avoid return a `Promise`. That means the `render(context, emitter)`:
+
+- Should not directly `return `, and
+- Should not be declared as `async`.
+
+{% note info Non Sync-Compatible Tags %}
+Non sync-compatible tags are also valid tags, will work just fine for asynchronous API calls. When called synchronously, tags that return a Promise will be rendered as [object Promise].
+{% endnote %}
+
+### Await Promises
+But LiquidJS is Promise-friendly, right? You can still call Promise-based functions and wait for that Promise within tag implementations. Just replace `await` with `yield` and keep `* render()` instead of `async render()`. e.g.
+
+```typescript
+import { TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'
+
+// Usage: {% upper "alice" %}
+// Output: ALICE
+engine.registerTag('upper', {
+ parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
+ this.str = tagToken.args
+ },
+ * render: function(ctx: Context) {
+ // _evalValue will behave synchronously when called by synchronous API
+ // in which case `ctx.sync == true`
+ var str = yield this.liquid._evalValue(this.str, ctx)
+ return str.toUpperCase()
+ }
+})
+```
+
+See this JSFiddle: .
+
+## Async-only Tags
+
+For tags that intended to be used only by async API, or those cannot be implemented synchronously, there's no difference between using generator-base syntax or async syntax. I'll call them *async-only tags*.
+
+For example, if the above `this.liquid._evalValue()` doesn't respect `ctx.sync` and always returns a Promise, even if the tag is implemented using `* render()` and `yield this.liquid._evalValue()`, it will be rendered as `