diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h
index 0e329bca17532f34f540b36893621fc83f32efef..de156d59279a34bd6cc800d95918ee42e3f07c2b 100644
--- a/sys/include/net/nanocoap.h
+++ b/sys/include/net/nanocoap.h
@@ -17,6 +17,7 @@
  * @brief       nanocoap API
  *
  * @author      Kaspar Schleiser <kaspar@schleiser.de>
+ * @author      Ken Bannister <kb2ma@runbox.com>
  */
 
 #ifndef NET_NANOCOAP_H
@@ -224,6 +225,18 @@ extern "C" {
 #define COAP_BLOCKWISE_SZX_MAX  (7)
 /** @} */
 
+/**
+ * @name coap_opt_finish() flag parameter values
+ *
+ * Directs packet/buffer updates when user finishes adding options
+ * @{
+ */
+/** @brief    no special handling required */
+#define COAP_OPT_FINISH_NONE     (0x0000)
+/** @brief    expect a payload to follow */
+#define COAP_OPT_FINISH_PAYLOAD  (0x0001)
+/** @} */
+
 /**
  * @brief   Raw CoAP PDU header structure
  */
@@ -390,6 +403,22 @@ ssize_t coap_handle_req(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_le
 ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token,
                        size_t token_len, unsigned code, uint16_t id);
 
+/**
+ * @brief   Initialize a packet struct, to build a message buffer
+ *
+ * @pre  buf              CoAP header already initialized
+ * @post pkt.flags        all zeroed
+ * @post pkt.payload      points to first byte after header
+ * @post pkt.payload_len  set to maximum space available for options + payload
+ *
+ * @param[out]   pkt        pkt to initialize
+ * @param[in]    buf        buffer to write for pkt, with CoAP header already
+ *                          initialized
+ * @param[in]    len        length of buf
+ * @param[in]    header_len length of header in buf, including token
+ */
+void coap_pkt_init(coap_pkt_t *pkt, uint8_t *buf, size_t len, size_t header_len);
+
 /**
  * @brief   Insert a CoAP option into buffer
  *
@@ -499,6 +528,52 @@ size_t coap_put_option_block1(uint8_t *buf, uint16_t lastonum, unsigned blknum,
  */
 size_t coap_put_block1_ok(uint8_t *pkt_pos, coap_block1_t *block1, uint16_t lastonum);
 
+/**
+ * @brief   Encode the given string as option(s) into pkt
+ *
+ * Use separator to split string into multiple options.
+ *
+ * @post pkt.payload advanced to first byte after option(s)
+ * @post pkt.payload_len reduced by option(s) length
+ *
+ * @param[in,out] pkt         pkt referencing target buffer
+ * @param[in]     optnum      option number to use
+ * @param[in]     string      string to encode as option
+ * @param[in]     separator   character used in @p string to separate parts
+ *
+ * @return        number of bytes written to buffer
+ * @return        -ENOSPC if no available options
+ */
+ssize_t coap_opt_add_string(coap_pkt_t *pkt, uint16_t optnum, const char *string, char separator);
+
+/**
+ * @brief   Encode the given uint option into pkt
+ *
+ * @post pkt.payload advanced to first byte after option
+ * @post pkt.payload_len reduced by option length
+ *
+ * @param[in,out] pkt         pkt referencing target buffer
+ * @param[in]     optnum      option number to use
+ * @param[in]     value       uint to encode
+ *
+ * @return        number of bytes written to buffer
+ * @return        <0 reserved for error but not implemented yet
+ */
+ssize_t coap_opt_add_uint(coap_pkt_t *pkt, uint16_t optnum, uint32_t value);
+
+/**
+ * @brief   Finalizes options as required and prepares for payload
+ *
+ * @post pkt.payload advanced to first available byte after options
+ * @post pkt.payload_len is maximum bytes available for payload
+ *
+ * @param[in,out] pkt         pkt to update
+ * @param[in]     flags       see COAP_OPT_FINISH... macros
+ *
+ * @return        total number of bytes written to buffer
+ */
+ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags);
+
 /**
  * @brief   Get content type from packet
  *
diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c
index e72bda605981b250c4304226e063405c8f4d944b..d5b9c0c7218779ffcbc33f99a55aba247eb7a258 100644
--- a/sys/net/application_layer/nanocoap/nanocoap.c
+++ b/sys/net/application_layer/nanocoap/nanocoap.c
@@ -383,6 +383,15 @@ ssize_t coap_build_hdr(coap_hdr_t *hdr, unsigned type, uint8_t *token, size_t to
     return sizeof(coap_hdr_t) + token_len;
 }
 
+void coap_pkt_init(coap_pkt_t *pkt, uint8_t *buf, size_t len, size_t header_len)
+{
+    memset(pkt, 0, sizeof(coap_pkt_t));
+    pkt->hdr = (coap_hdr_t *)buf;
+    pkt->token = buf + sizeof(coap_hdr_t);
+    pkt->payload = buf + header_len;
+    pkt->payload_len = len - header_len;
+}
+
 static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end)
 {
     uint8_t *pkt_pos = *pkt_pos_ptr;
@@ -595,6 +604,85 @@ size_t coap_put_option_uri(uint8_t *buf, uint16_t lastonum, const char *uri, uin
     return bufpos - buf;
 }
 
+/* Common functionality for addition of an option */
+static ssize_t _add_opt_pkt(coap_pkt_t *pkt, uint16_t optnum, uint8_t *val,
+                            size_t val_len)
+{
+    assert(pkt->options_len < NANOCOAP_NOPTS_MAX);
+
+    uint16_t lastonum = (pkt->options_len)
+            ? pkt->options[pkt->options_len - 1].opt_num : 0;
+    assert(optnum >= lastonum);
+
+    size_t optlen = coap_put_option(pkt->payload, lastonum, optnum, val, val_len);
+    assert(pkt->payload_len > optlen);
+
+    pkt->options[pkt->options_len].opt_num = optnum;
+    pkt->options[pkt->options_len].offset = pkt->payload - (uint8_t *)pkt->hdr;
+    pkt->options_len++;
+    pkt->payload += optlen;
+    pkt->payload_len -= optlen;
+
+    return optlen;
+}
+
+ssize_t coap_opt_add_string(coap_pkt_t *pkt, uint16_t optnum, const char *string,
+                           char separator)
+{
+    size_t unread_len = strlen(string);
+    if (!unread_len) {
+        return 0;
+    }
+    char *uripos = (char *)string;
+    size_t write_len = 0;
+
+    while (unread_len) {
+        size_t part_len;
+        uripos++;
+        uint8_t *part_start = (uint8_t *)uripos;
+
+        while (unread_len--) {
+            if ((*uripos == separator) || (*uripos == '\0')) {
+                break;
+            }
+            uripos++;
+        }
+
+        part_len = (uint8_t *)uripos - part_start;
+
+        if (part_len) {
+            if (pkt->options_len == NANOCOAP_NOPTS_MAX) {
+                return -ENOSPC;
+            }
+            write_len += _add_opt_pkt(pkt, optnum, part_start, part_len);
+        }
+    }
+
+    return write_len;
+}
+
+ssize_t coap_opt_add_uint(coap_pkt_t *pkt, uint16_t optnum, uint32_t value)
+{
+    uint32_t tmp = value;
+    unsigned tmp_len = _encode_uint(&tmp);
+    return _add_opt_pkt(pkt, optnum, (uint8_t *)&tmp, tmp_len);
+}
+
+ssize_t coap_opt_finish(coap_pkt_t *pkt, uint16_t flags)
+{
+    if (flags & COAP_OPT_FINISH_PAYLOAD) {
+        assert(pkt->payload_len > 1);
+
+        *pkt->payload++ = 0xFF;
+        pkt->payload_len--;
+    }
+    else {
+        pkt->payload_len = 0;
+    }
+
+    return pkt->payload - (uint8_t *)pkt->hdr;
+}
+
 ssize_t coap_well_known_core_default_handler(coap_pkt_t *pkt, uint8_t *buf, \
                                              size_t len, void *context)
 {
diff --git a/tests/unittests/tests-nanocoap/tests-nanocoap.c b/tests/unittests/tests-nanocoap/tests-nanocoap.c
index d5694344359259e8e34afd900cd5fad0bc2ccb7f..b62e4ed85e5f4bb1e04aba4060efa4a4cb3fccdf 100644
--- a/tests/unittests/tests-nanocoap/tests-nanocoap.c
+++ b/tests/unittests/tests-nanocoap/tests-nanocoap.c
@@ -13,6 +13,7 @@
  */
 #include <errno.h>
 #include <stdint.h>
+#include <stdbool.h>
 
 #include "embUnit.h"
 
@@ -45,10 +46,111 @@ static void test_nanocoap__hdr(void)
     TEST_ASSERT_EQUAL_STRING((char *)path, (char *)path_tmp);
 }
 
+/*
+ * Client GET request with simple path. Test request generation.
+ * Request /time resource from libcoap example
+ */
+static void test_nanocoap__get_req(void)
+{
+    uint8_t buf[128];
+    coap_pkt_t pkt;
+    uint16_t msgid = 0xABCD;
+    uint8_t token[2] = {0xDA, 0xEC};
+    char path[] = "/time";
+    size_t total_hdr_len = 6;
+    size_t total_opt_len = 5;
+
+    size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON,
+                                &token[0], 2, COAP_METHOD_GET, msgid);
+    TEST_ASSERT_EQUAL_INT(total_hdr_len, len);
+
+    coap_pkt_init(&pkt, &buf[0], sizeof(buf), len);
+
+    TEST_ASSERT_EQUAL_INT(COAP_METHOD_GET, coap_get_code(&pkt));
+    TEST_ASSERT_EQUAL_INT(2, coap_get_token_len(&pkt));
+    TEST_ASSERT_EQUAL_INT(total_hdr_len, coap_get_total_hdr_len(&pkt));
+    TEST_ASSERT_EQUAL_INT(COAP_TYPE_NON, coap_get_type(&pkt));
+    TEST_ASSERT_EQUAL_INT(122, pkt.payload_len);
+
+    len = coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/');
+    TEST_ASSERT_EQUAL_INT(total_opt_len, len);
+
+    char uri[10] = {0};
+    coap_get_uri(&pkt, (uint8_t *)&uri[0]);
+    TEST_ASSERT_EQUAL_STRING((char *)path, (char *)uri);
+
+    len = coap_opt_finish(&pkt, COAP_OPT_FINISH_NONE);
+    TEST_ASSERT_EQUAL_INT(total_hdr_len + total_opt_len, len);
+}
+
+/*
+ * Builds on get_req test, to test payload and Content-Format option.
+ */
+static void test_nanocoap__put_req(void)
+{
+    uint8_t buf[128];
+    coap_pkt_t pkt;
+    uint16_t msgid = 0xABCD;
+    uint8_t token[2] = {0xDA, 0xEC};
+    char path[] = "/value";
+    size_t total_hdr_len = 6;
+    size_t uri_opt_len = 6;
+    size_t fmt_opt_len = 1;
+
+    size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON,
+                                &token[0], 2, COAP_METHOD_PUT, msgid);
+    TEST_ASSERT_EQUAL_INT(total_hdr_len, len);
+
+    coap_pkt_init(&pkt, &buf[0], sizeof(buf), len);
+
+    TEST_ASSERT_EQUAL_INT(COAP_METHOD_PUT, coap_get_code(&pkt));
+    TEST_ASSERT_EQUAL_INT(122, pkt.payload_len);
+
+    len = coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/');
+    TEST_ASSERT_EQUAL_INT(uri_opt_len, len);
+
+    len = coap_opt_add_uint(&pkt, COAP_OPT_CONTENT_FORMAT, COAP_FORMAT_TEXT);
+    TEST_ASSERT_EQUAL_INT(fmt_opt_len, len);
+    TEST_ASSERT_EQUAL_INT(COAP_FORMAT_TEXT, coap_get_content_type(&pkt));
+
+    len = coap_opt_finish(&pkt, COAP_OPT_FINISH_PAYLOAD);
+    TEST_ASSERT_EQUAL_INT(total_hdr_len + uri_opt_len + fmt_opt_len + 1, len);
+    TEST_ASSERT_EQUAL_INT(0xFF, *(pkt.payload - 1));
+    TEST_ASSERT_EQUAL_INT(&buf[0] + 128 - pkt.payload, pkt.payload_len);
+}
+
+/*
+ * Builds on get_req test, to test path with multiple segments.
+ */
+static void test_nanocoap__get_multi_path(void)
+{
+    uint8_t buf[128];
+    coap_pkt_t pkt;
+    uint16_t msgid = 0xABCD;
+    uint8_t token[2] = {0xDA, 0xEC};
+    char path[] = "/ab/cde";
+    size_t uri_opt_len = 7;
+
+    size_t len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON,
+                                &token[0], 2, COAP_METHOD_GET, msgid);
+
+    coap_pkt_init(&pkt, &buf[0], sizeof(buf), len);
+
+    len = coap_opt_add_string(&pkt, COAP_OPT_URI_PATH, &path[0], '/');
+    TEST_ASSERT_EQUAL_INT(uri_opt_len, len);
+
+    char uri[10] = {0};
+    coap_get_uri(&pkt, (uint8_t *)&uri[0]);
+    TEST_ASSERT_EQUAL_STRING((char *)path, (char *)uri);
+}
+
 Test *tests_nanocoap_tests(void)
 {
     EMB_UNIT_TESTFIXTURES(fixtures) {
         new_TestFixture(test_nanocoap__hdr),
+        new_TestFixture(test_nanocoap__get_req),
+        new_TestFixture(test_nanocoap__put_req),
+        new_TestFixture(test_nanocoap__get_multi_path),
     };
 
     EMB_UNIT_TESTCALLER(nanocoap_tests, NULL, NULL, fixtures);