diff --git a/pd/doc/5.reference/savestate-example.pd b/pd/doc/5.reference/savestate-example.pd
new file mode 100644
index 0000000000000000000000000000000000000000..dc10b95c5fb167c7746026caf8b7df1007e4292c
--- /dev/null
+++ b/pd/doc/5.reference/savestate-example.pd
@@ -0,0 +1,49 @@
+#N canvas 112 69 693 505 12;
+#X obj 36 158 osc~;
+#X floatatom 103 72 7 0 10000 1 frequency - -;
+#X obj 37 336 outlet~;
+#X floatatom 103 97 7 0 10000 1 bandwidth - -;
+#X obj 109 152 max 1;
+#X obj 109 178 t b f;
+#X obj 37 279 *~;
+#X obj 37 307 expr~ exp(-$v1*$v1);
+#X obj 36 125 * 0.5;
+#X obj 348 242 f;
+#X obj 348 271 pack;
+#X obj 288 145 savestate;
+#X obj 288 316 unpack;
+#X obj 103 207 / 1;
+#X text 63 19 this pulse generator is used by the savestate help file.
+;
+#X text 80 388 This is a waveshaping pulse generator using a Gaussian
+table lookup \, as described in Theory and Technique of electronic
+music \, chapter 6 The controls are "F" to set fundamental frequency
+\, and "BW" to set bandwidth \, both in cycles per second.;
+#X text 362 145 right outlet gets bang when parent patch is saved.
+In response we send 'savestate' one or more lists that will be stored
+as part of the parent patch, f 35;
+#X text 344 304 Left outlet returns the list or lists to the abstraction
+which can use them to restore its state when it is reloaded., f 38
+;
+#X text 389 237 In response \, we build and send a list of parameters
+we want to save, f 34;
+#X connect 0 0 6 0;
+#X connect 1 0 8 0;
+#X connect 1 0 9 1;
+#X connect 3 0 10 1;
+#X connect 3 0 13 0;
+#X connect 4 0 5 0;
+#X connect 5 0 13 0;
+#X connect 5 1 13 1;
+#X connect 6 0 7 0;
+#X connect 7 0 2 0;
+#X connect 8 0 0 0;
+#X connect 8 0 4 0;
+#X connect 9 0 10 0;
+#X connect 10 0 11 0;
+#X connect 11 0 12 0;
+#X connect 11 1 9 0;
+#X connect 12 0 1 0;
+#X connect 12 1 3 0;
+#X connect 13 0 6 1;
+#X coords 0 -1 1 1 125 70 1 100 50;
diff --git a/pd/doc/5.reference/savestate-help.pd b/pd/doc/5.reference/savestate-help.pd
new file mode 100644
index 0000000000000000000000000000000000000000..83ffe822025608710d64631217b3d0b368b013d4
--- /dev/null
+++ b/pd/doc/5.reference/savestate-help.pd
@@ -0,0 +1,34 @@
+#N canvas 711 48 578 672 12;
+#X obj 82 73 savestate-example;
+#A saved 110 660;
+#X text 223 71 open the abstraction at left (right- or CTRL- click
+and select "open" in popup menu) to see how the savestate object is
+used from within., f 46;
+#X obj 82 158 savestate-example;
+#A saved 221 440;
+#X text 223 162 parameters for different copies of the abstraction
+are saved and restored independently., f 32;
+#X text 348 610 updated for Pd version 0.49.;
+#X text 81 445 The abstraction may itself be modified at will without
+disturbing the saved states of its copies in any calling patches \,
+as long as the usage of the saved and restored lists is kept compatible.
+;
+#X text 80 512 Multiple savestate objects are not differentated - they
+all receive all lists sent to any one of them.;
+#X text 81 550 Hint: 'text' objects can be saved/restored using 'text
+tolist' and 'text fromlist'.;
+#X text 82 422 Abstractions within 'clone' objects are not handled.
+;
+#X obj 75 27 savestate;
+#X text 148 26 - save and restore run-time state from within an abstraction
+, f 70;
+#X text 81 250 The savestate object is used inside abstractions to
+save their state as they are used in a calling (parent) patch. When
+the parent patch (such as this one \, which calls the "savestate-example"
+abstraction) is saved \, the included savestate object sends a 'bang'
+message out its right outlet \, with which the abstraction may respond
+by presenting one or more 'list' messages back to the 'savestate' object.
+These lists are saved as part of the calling patch. If the calling
+patch is reopened later \, the lists are sent out the left outlet of
+the savestate object. The abstraction can then use them to restore
+its state.;
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index b4e4fd4c86783196027080d8919aa75828132d11..e29d802d7a92cac7aa11515eabde57056edd7efd 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -1391,6 +1391,8 @@ static void canvas_relocate(t_canvas *x, t_symbol *canvasgeom,
 void canvas_popabstraction(t_canvas *x)
 {
     newest = &x->gl_pd;
+    gensym("#A")->s_thing = 0;
+    pd_bind(newest, gensym("#A"));
     pd_popsym(&x->gl_pd);
     //x->gl_loading = 1;
     //fprintf(stderr,"loading = 1 .x%lx owner=.x%lx\n", x, x->gl_owner);
diff --git a/pd/src/g_readwrite.c b/pd/src/g_readwrite.c
index a3affb88f8ac10b4cd97e6e523a18e420c7387e3..672d536bb12c9ba32c39d515fedfafc944271bf3 100644
--- a/pd/src/g_readwrite.c
+++ b/pd/src/g_readwrite.c
@@ -17,6 +17,91 @@ file format as in the dialog window for data.
 #include "g_canvas.h"
 #include <string.h>
 
+/* object to assist in saving state by abstractions */
+static t_class *savestate_class;
+
+typedef struct _savestate
+{
+    t_object x_obj;
+    t_outlet *x_stateout;
+    t_outlet *x_bangout;
+    t_binbuf *x_savetobuf;
+} t_savestate;
+
+static void *savestate_new(void)
+{
+    t_savestate *x = (t_savestate *)pd_new(savestate_class);
+    x->x_stateout = outlet_new(&x->x_obj, &s_list);
+    x->x_bangout = outlet_new(&x->x_obj, &s_bang);
+    x->x_savetobuf = 0;
+    return (x);
+}
+
+    /* call this when the owning abstraction's parent patch is saved so we
+     *     can add state-restoring messages to binbuf */
+static void savestate_doit(t_savestate *x, t_binbuf *b)
+{
+    x->x_savetobuf = b;
+    outlet_bang(x->x_bangout);
+    x->x_savetobuf = 0;
+}
+
+    /* called by abstraction in response to savestate_doit(); lists received
+       here are added to the parent patch's save buffer after the line that will
+       create the abstraction, addressed to "#A" which will be this patch after
+       it is recreated by reopening the parent patch, pasting, or "undo". */
+static void savestate_list(t_savestate *x, t_symbol *s, int argc, t_atom *argv)
+{
+    if (x->x_savetobuf)
+    {
+        binbuf_addv(x->x_savetobuf, "ss", gensym("#A"), gensym("saved"));
+        binbuf_add(x->x_savetobuf, argc, argv);
+        binbuf_addv(x->x_savetobuf, ";");
+    }
+    else pd_error(x, "savestate: ignoring message sent when not saving parent");
+}
+
+static void savestate_setup(void)
+{
+    savestate_class = class_new(gensym("savestate"),
+        (t_newmethod)savestate_new, 0, sizeof(t_savestate), 0, 0);
+    class_addlist(savestate_class, savestate_list);
+}
+
+void canvas_statesavers_doit(t_glist *x, t_binbuf *b)
+{
+    t_gobj *g;
+    for (g = x->gl_list; g; g = g->g_next)
+    {
+        if (g->g_pd == savestate_class)
+        {
+            savestate_doit((t_savestate *)g, b);
+        }
+        else if (g->g_pd == canvas_class &&
+            !canvas_isabstraction((t_canvas *)g))
+        {
+            canvas_statesavers_doit((t_glist *)g, b);
+        }
+    }
+}
+
+void canvas_saved(t_glist *x, t_symbol *s, int argc, t_atom *argv)
+{
+    t_gobj *g;
+    for (g = x->gl_list; g; g = g->g_next)
+    {
+        if (g->g_pd == savestate_class)
+        {
+            outlet_list(((t_savestate *)g)->x_stateout, 0, argc, argv);
+        }
+        else if (g->g_pd == canvas_class &&
+            !canvas_isabstraction((t_canvas *)g))
+        {
+            canvas_saved((t_glist *)g, s, argc, argv);
+        }
+    }
+}
+
 void canvas_savedeclarationsto(t_canvas *x, t_binbuf *b);
 
     /* the following routines read "scalars" from a file into a canvas. */
@@ -921,6 +1006,7 @@ static void canvas_menuprint(t_canvas *x)
 
 void g_readwrite_setup(void)
 {
+    savestate_setup();
     class_addmethod(canvas_class, (t_method)glist_write,
         gensym("write"), A_SYMBOL, A_DEFSYM, A_NULL);
     class_addmethod(canvas_class, (t_method)glist_read,
@@ -931,6 +1017,8 @@ void g_readwrite_setup(void)
         gensym("savetofile"), A_SYMBOL, A_SYMBOL, A_DEFFLOAT, 0);
     class_addmethod(canvas_class, (t_method)canvas_saveto,
         gensym("saveto"), A_CANT, 0);
+    class_addmethod(canvas_class, (t_method)canvas_saved,
+        gensym("saved"), A_GIMME, 0);
 /* ------------------ from the menu ------------------------- */
     class_addmethod(canvas_class, (t_method)canvas_menusave,
         gensym("menusave"), A_DEFFLOAT, 0);
diff --git a/pd/src/g_text.c b/pd/src/g_text.c
index 4eae176faf5ab4d41d05110fdea158b946211b36..2a1ef4879702c3c1e9827a7f05ebdf2e8e17111e 100644
--- a/pd/src/g_text.c
+++ b/pd/src/g_text.c
@@ -2182,6 +2182,7 @@ static int text_click(t_gobj *z, struct _glist *glist,
     else return (0);
 }
 
+void canvas_statesavers_doit(t_glist *x, t_binbuf *b);
 void text_save(t_gobj *z, t_binbuf *b)
 {
     //fprintf(stderr, "text_save\n");
@@ -2211,6 +2212,7 @@ void text_save(t_gobj *z, t_binbuf *b)
         //fprintf(stderr, "this must be it\n");
         binbuf_addbinbuf(b, x->te_binbuf);
         //fprintf(stderr, "DONE this must be it\n");
+
     }
     else if (x->te_type == T_MESSAGE)
     {
@@ -2292,6 +2294,13 @@ void text_save(t_gobj *z, t_binbuf *b)
             binbuf_addv(b, ",si", gensym("f"), (int)x->te_width);
     }
     binbuf_addv(b, ";");
+
+        /* if an abstraction, give it a chance to save state */
+    if (pd_class(&x->te_pd) == canvas_class &&
+        canvas_isabstraction((t_canvas *)x))
+    {
+        canvas_statesavers_doit((t_glist *)x, b);
+    }
 }
 
     /* this one is for everyone but "gatoms"; it's imposed in m_class.c */